REST APIの設計方法に関するいくつかのチュートリアルに従ってきましたが、まだいくつかの大きな疑問符があります。これらのチュートリアルはすべて、比較的単純な階層のリソースを示しています。これらのチュートリアルで使用されている原則が、より複雑な階層にどのように適用されるかを知りたいのです。さらに、彼らは非常に高い/建築レベルにとどまります。永続層は言うまでもなく、関連するコードはほとんど表示されません。Gavin Kingが言ったように、私は特にデータベースのロード/パフォーマンスについて心配しています:
開発のすべての段階でデータベースに注意を払えば、労力を節約できます
アプリケーションがのトレーニングを提供するとしますCompanies
。Companies
持っDepartments
ていOffices
ます。Departments
持っていEmployees
ます。Employees
持っているSkills
とCourses
し、特定のLevel
特定のスキルのは、いくつかのコースのために署名することができるように要求されています。階層は次のとおりですが、
-Companies
-Departments
-Employees
-PersonalInformation
-Address
-Skills (quasi-static data)
-Levels (quasi-static data)
-Courses
-Address
-Offices
-Address
パスは次のようになります。
companies/1/departments/1/employees/1/courses/1
companies/1/offices/1/employees/1/courses/1
リソースを取得する
したがって、会社を返すときに、階層全体を返すことはできませんcompanies/1/departments/1/employees/1/courses/1
+ companies/1/offices/../
。部門または展開された部門へのリンクのリストを返す可能性があり、このレベルでも同じ決定を行う必要があります。部門の従業員または展開された従業員へのリンクのリストを返しますか?それは部門や従業員などの数に依存します。
質問1:私の考えは正しいですか。「階層をどこで切るか」は、私がしなければならない典型的なエンジニアリング上の決定ですか
ここで、尋ねられたときGET companies/id
に、部署のコレクションへのリンクのリストと展開されたオフィス情報を返すことにします。私の会社には多くのオフィスがないので、テーブルOffices
と一緒に参加するAddresses
ことは大したことではありません。応答の例:
GET /companies/1
200 OK
{
"_links":{
"self" : {
"href":"http://trainingprovider.com:8080/companies/1"
},
"offices": [
{ "href": "http://trainingprovider.com:8080/companies/1/offices/1"},
{ "href": "http://trainingprovider.com:8080/companies/1/offices/2"},
{ "href": "http://trainingprovider.com:8080/companies/1/offices/3"}
],
"departments": [
{ "href": "http://trainingprovider.com:8080/companies/1/departments/1"},
{ "href": "http://trainingprovider.com:8080/companies/1/departments/2"},
{ "href": "http://trainingprovider.com:8080/companies/1/departments/3"}
]
}
"name":"Acme",
"industry":"Manufacturing",
"description":"Some text here",
"offices": {
"_meta":{
"href":"http://trainingprovider.com:8080/companies/1/offices"
// expanded offices information here
}
}
}
コードレベルでは、これは(Hibernateを使用すると、他のプロバイダーとの関係がわかりませんが、それはほとんど同じだと思います)、コレクションをDepartment
フィールドとしてCompany
クラスに配置しないことを意味します。
- 言ったように、私はそれをロードして
Company
いないので、熱心にロードしたくありません - そして、それを熱心にロードしない場合は、削除することもできます。これは、Companyをロードした後に永続コンテキストが閉じ、後でそれをロードしようとしても意味がないためです(
LazyInitializationException
)。
その後、私が出してあげるInteger companyId
にはDepartment
、私が会社にするにはカテゴリーを追加できるように、クラス。
また、すべての部門のIDを取得する必要があります。DBへの別のヒットですが、重いものではないので、大丈夫です。コードは次のようになります。
@Service
@Path("/companies")
public class CompanyResource {
@Autowired
private CompanyService companyService;
@Autowired
private CompanyParser companyParser;
@Path("/{id}")
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response findById(@PathParam("id") Integer id) {
Optional<Company> company = companyService.findById(id);
if (!company.isPresent()) {
throw new CompanyNotFoundException();
}
CompanyResponse companyResponse = companyParser.parse(company.get());
// Creates a DTO with a similar structure to Company, and recursivelly builds
// sub-resource DTOs such as OfficeDTO
Set<Integer> departmentIds = companyService.getDepartmentIds(id);
// "SELECT id FROM departments WHERE companyId = id"
// add list of links to the response
return Response.ok(companyResponse).build();
}
}
@Entity
@Table(name = "companies")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String industry;
@OneToMany(fetch = EAGER, cascade = {ALL}, orphanRemoval = true)
@JoinColumn(name = "companyId_fk", referencedColumnName = "id", nullable = false)
private Set<Office> offices = new HashSet<>();
// getters and setters
}
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer companyId;
@OneToMany(fetch = EAGER, cascade = {ALL}, orphanRemoval = true)
@JoinColumn(name = "departmentId", referencedColumnName = "id", nullable = false)
private Set<Employee> employees = new HashSet<>();
// getters and setters
}
リソースの更新
更新操作では、PUT
またはでエンドポイントを公開できますPOST
。私PUT
はべき等であるため、部分的な更新を許可できません。ただし、会社の説明フィールドを変更する場合は、リソース全体を送信する必要があります。それは肥大化しすぎているようです。従業員のを更新する場合も同様PersonalInformation
です。Skills
+ Courses
をすべて一緒に送信する必要があるのは理にかなっているとは思いません。
質問2:PUTは、きめの細かいリソースに使用されるだけですか?
ログで、エンティティのマージ時にHibernateが一連のSELECT
クエリを実行することを確認しました。何かが変更されたかどうかを確認し、必要な情報を更新するだけだと思います。階層内のエンティティが上になるほど、クエリはより重く、より複雑になります。しかし、いくつかの情報源は、粗いリソースを使用することを勧めています。繰り返しますが、多すぎるテーブルの数を確認し、リソースの粒度とDBクエリの複雑さの間の妥協点を見つける必要があります。
質問3:これは、エンジニアリングの決定を「どこで削減するかを知る」だけなのですか、それとも何か不足していますか?
質問4:これはそうですか、そうでない場合は、RESTサービスを設計し、リソースの粒度、クエリの複雑さ、ネットワークの雑談の間の妥協点を探す際の正しい「思考プロセス」は何ですか?