Saya telah mengikuti beberapa tutorial tentang cara mendesain API REST, tetapi saya masih memiliki beberapa tanda tanya besar. Semua tutorial ini menunjukkan sumber daya dengan hierarki yang relatif sederhana, dan saya ingin tahu bagaimana prinsip-prinsip yang digunakan di dalamnya berlaku untuk yang lebih kompleks. Selain itu, mereka tinggal di tingkat arsitektur yang sangat tinggi. Mereka nyaris tidak menunjukkan kode yang relevan, apalagi lapisan kegigihan. Saya secara khusus prihatin dengan beban / kinerja basis data, seperti yang dikatakan Gavin King :
Anda akan menghemat usaha jika Anda memperhatikan database di semua tahap pengembangan
Katakanlah aplikasi saya akan memberikan pelatihan untuk Companies
. Companies
miliki Departments
dan Offices
. Departments
miliki Employees
. Employees
miliki Skills
dan Courses
, dan Level
keterampilan tertentu diperlukan untuk dapat mendaftar untuk beberapa kursus. Hirarki adalah sebagai berikut, tetapi dengan:
-Companies
-Departments
-Employees
-PersonalInformation
-Address
-Skills (quasi-static data)
-Levels (quasi-static data)
-Courses
-Address
-Offices
-Address
Paths akan menjadi sesuatu seperti:
companies/1/departments/1/employees/1/courses/1
companies/1/offices/1/employees/1/courses/1
Mengambil sumber daya
Jadi ok, ketika mengembalikan perusahaan, saya jelas tidak mengembalikan seluruh hirarki companies/1/departments/1/employees/1/courses/1
+ companies/1/offices/../
. Saya mungkin mengembalikan daftar tautan ke departemen atau departemen yang diperluas, dan harus mengambil keputusan yang sama di tingkat ini: apakah saya mengembalikan daftar tautan ke karyawan departemen atau karyawan yang diperluas? Itu akan tergantung pada jumlah departemen, karyawan, dll.
Pertanyaan 1 : Apakah pemikiran saya benar, apakah "tempat untuk memotong hierarki" adalah keputusan rekayasa yang harus saya buat?
Sekarang katakanlah ketika ditanya GET companies/id
, saya memutuskan untuk mengembalikan daftar tautan ke koleksi departemen, dan informasi kantor yang diperluas. Perusahaan saya tidak memiliki banyak kantor, jadi bergabunglah dengan tabel Offices
dan Addresses
seharusnya tidak menjadi masalah besar. Contoh tanggapan:
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
}
}
}
Pada level kode, ini menyiratkan bahwa (menggunakan Hibernate, saya tidak yakin bagaimana dengan penyedia lain, tapi saya kira itu hampir sama) Saya tidak akan menempatkan koleksi Department
sebagai bidang di Company
kelas saya , karena:
- Seperti yang saya katakan, saya tidak memuatnya
Company
, jadi saya tidak ingin memuatnya dengan penuh semangat - Dan jika saya tidak memuatnya dengan penuh semangat, saya mungkin juga menghapusnya, karena konteks kegigihan akan ditutup setelah saya memuat Perusahaan dan tidak ada gunanya mencoba memuatnya setelah itu (
LazyInitializationException
).
Kemudian, saya akan menempatkan Integer companyId
di Department
kelas, sehingga saya dapat menambahkan departemen ke perusahaan.
Juga, saya perlu mendapatkan id dari semua departemen. Hit lain ke DB tapi bukan yang berat, jadi harusnya ok. Kode tersebut dapat terlihat seperti:
@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
}
Memperbarui sumber daya
Untuk operasi pembaruan, saya dapat mengekspos titik akhir dengan PUT
atau POST
. Karena saya ingin saya PUT
menjadi idempoten, saya tidak dapat mengizinkan pembaruan parsial . Tetapi kemudian, jika saya ingin memodifikasi bidang deskripsi perusahaan, saya perlu mengirim seluruh perwakilan sumber daya. Itu sepertinya terlalu membengkak. Hal yang sama ketika memperbarui karyawan PersonalInformation
. Saya tidak berpikir masuk akal harus mengirim semua Skills
+ Courses
bersama-sama dengan itu.
Pertanyaan 2 : Apakah PUT hanya digunakan untuk sumber daya halus?
Saya telah melihat di dalam log bahwa, ketika menggabungkan suatu entitas, Hibernate mengeksekusi sekelompok SELECT
query. Saya kira itu hanya untuk memeriksa apakah ada yang berubah dan memperbarui informasi apa pun yang diperlukan. Semakin tinggi entitas dalam hierarki, semakin berat dan semakin kompleks kueri. Tetapi beberapa sumber menyarankan untuk menggunakan sumber daya berbutir kasar . Jadi sekali lagi, saya perlu memeriksa berapa banyak tabel yang terlalu banyak, dan menemukan kompromi antara granularity sumber daya dan kompleksitas permintaan DB.
Pertanyaan 3 : Apakah ini sekadar keputusan teknis "tahu di mana harus memotong" atau apakah saya kehilangan sesuatu?
Pertanyaan 4 : Apakah ini, atau jika tidak, apa "proses berpikir" yang tepat ketika merancang layanan REST dan mencari kompromi antara granularitas sumber daya, kompleksitas kueri, dan kedekatan jaringan?