Làm cách nào để thiết kế API REST phức tạp khi xem xét hiệu suất DB?


8

Tôi đã theo dõi một số hướng dẫn về cách thiết kế API REST, nhưng tôi vẫn còn một số dấu hỏi lớn. Tất cả các hướng dẫn này cho thấy các tài nguyên với hệ thống phân cấp tương đối đơn giản và tôi muốn biết làm thế nào các nguyên tắc được sử dụng trong những nguyên tắc đó áp dụng cho một thứ phức tạp hơn. Hơn nữa, họ ở mức rất cao / kiến ​​trúc. Họ hầu như không hiển thị bất kỳ mã có liên quan, chứ chưa nói đến lớp kiên trì. Tôi đặc biệt quan tâm đến tải / hiệu suất cơ sở dữ liệu, như Gavin King đã nói :

bạn sẽ tiết kiệm công sức nếu bạn chú ý đến cơ sở dữ liệu ở tất cả các giai đoạn phát triển

Hãy nói rằng ứng dụng của tôi sẽ cung cấp đào tạo cho Companies. CompaniesDepartmentsOffices. DepartmentsEmployees. EmployeesSkillsCourses, và một Levelsố kỹ năng nhất định được yêu cầu để có thể đăng ký một số khóa học. Hệ thống phân cấp như sau, nhưng với:

-Companies
  -Departments
    -Employees
      -PersonalInformation
        -Address
      -Skills (quasi-static data)
        -Levels (quasi-static data)
      -Courses
        -Address
  -Offices
    -Address

Đường dẫn sẽ là một cái gì đó như:

companies/1/departments/1/employees/1/courses/1
companies/1/offices/1/employees/1/courses/1

Lấy tài nguyên

Vì vậy, ok, khi trở lại một công ty, tôi rõ ràng không trả lại toàn bộ phân cấp companies/1/departments/1/employees/1/courses/1+ companies/1/offices/../. Tôi có thể trả về một danh sách các liên kết đến các phòng ban hoặc các phòng ban được mở rộng và phải thực hiện cùng một quyết định ở cấp độ này: tôi có trả lại danh sách các liên kết cho nhân viên của bộ phận hoặc nhân viên mở rộng không? Điều đó sẽ phụ thuộc vào số lượng phòng ban, nhân viên, v.v.

Câu hỏi 1 : Suy nghĩ của tôi có đúng không, "nơi cắt giảm thứ bậc" là một sự phân rã kỹ thuật điển hình mà tôi cần thực hiện?

Bây giờ hãy nói rằng khi được hỏi GET companies/id, tôi quyết định trả về một danh sách các liên kết đến bộ sưu tập của bộ phận và thông tin văn phòng mở rộng. Các công ty của tôi không có nhiều văn phòng, vì vậy việc tham gia với các bảng OfficesAddresseskhông nên là vấn đề lớn. Ví dụ về phản ứng:

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
    }
  }
}

Ở cấp độ mã, điều này ngụ ý rằng (sử dụng Hibernate, tôi không chắc nó như thế nào với các nhà cung cấp khác, nhưng tôi đoán điều đó khá giống nhau) Tôi sẽ không đặt một bộ sưu tập Departmentnhư một trường trong Companylớp của mình , bởi vì:

  • Như đã nói, tôi không tải nó với Company, vì vậy tôi không muốn tải nó một cách háo hức
  • Và nếu tôi không tải nó một cách háo hức, tôi cũng có thể xóa nó, bởi vì bối cảnh tồn tại sẽ đóng sau khi tôi tải Công ty và không có lý do gì để cố tải nó sau đó ( LazyInitializationException).

Sau đó, tôi sẽ đưa một người Integer companyIdvào Departmentlớp, để tôi có thể thêm một bộ phận vào một công ty.

Ngoài ra, tôi cần lấy id của tất cả các phòng ban. Một cú đánh khác vào DB nhưng không phải là một cú nặng, nên sẽ ổn thôi. Mã này có thể trông như:

@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
}

Cập nhật tài nguyên

Đối với hoạt động cập nhật, tôi có thể hiển thị điểm cuối bằng PUThoặc POST. Vì tôi muốn mình bình PUTtĩnh, tôi không thể cho phép cập nhật một phần . Nhưng sau đó, nếu tôi muốn sửa đổi trường mô tả của công ty, tôi cần gửi toàn bộ đại diện tài nguyên. Điều đó dường như quá phồng lên. Tương tự khi cập nhật của nhân viên PersonalInformation. Tôi không nghĩ việc gửi tất cả Skills+ Coursescùng với điều đó là hợp lý.

Câu hỏi 2 : PUT chỉ được sử dụng cho các tài nguyên hạt mịn?

Tôi đã thấy trong nhật ký rằng, khi hợp nhất một thực thể, Hibernate thực thi một loạt các SELECTtruy vấn. Tôi đoán đó chỉ là để kiểm tra xem có gì thay đổi hay không và cập nhật bất cứ thông tin nào cần thiết. Thực thể càng cao trong hệ thống phân cấp, các truy vấn càng nặng và phức tạp. Nhưng một số nguồn khuyên nên sử dụng tài nguyên hạt thô . Vì vậy, một lần nữa, tôi sẽ cần kiểm tra có bao nhiêu bảng quá nhiều và tìm ra sự thỏa hiệp giữa độ chi tiết tài nguyên và độ phức tạp của truy vấn DB.

Câu hỏi 3 : Đây chỉ là một sự phân rã kỹ thuật "biết cắt giảm" hay tôi đang thiếu thứ gì?

Câu hỏi 4 : Đây có phải là, hoặc nếu không, "quá trình suy nghĩ" đúng đắn khi thiết kế dịch vụ REST và tìm kiếm sự thỏa hiệp giữa mức độ chi tiết của tài nguyên, độ phức tạp của truy vấn và độ chói của mạng là gì?


1
1. Có; bởi vì các cuộc gọi REST rất tốn kém, điều quan trọng là phải thử và có được độ chi tiết phù hợp.
Robert Harvey

1
2. Không. Động từ PUT không liên quan gì đến độ chi tiết, mỗi se.
Robert Harvey

1
3. Có. Không, bạn không thiếu thứ gì.
Robert Harvey

1
4. Suy nghĩ đúng đắn là "làm những gì đáp ứng tốt nhất các yêu cầu của bạn về khả năng mở rộng, hiệu suất, khả năng bảo trì và các vấn đề khác." Điều này có thể yêu cầu một số thử nghiệm để tìm ra điểm ngọt ngào.
Robert Harvey

4
Quá lâu. Không đọc. Điều này có thể được nhổ vào 4 câu hỏi thực tế?
MetaFight

Câu trả lời:


7

Tôi nghĩ rằng bạn có sự phức tạp bởi vì bạn đang bắt đầu với sự phức tạp quá mức:

Đường dẫn sẽ là một cái gì đó như:

companies/1/departments/1/employees/1/courses/1
companies/1/offices/1/employees/1/courses/1

Thay vào đó tôi sẽ giới thiệu lược đồ URL đơn giản hơn như thế này:

GET companies/
    Returns a list of companies, for each company 
    return short essential info (ID, name, maybe industry)
GET companies/1
    Returns single company info like this:

    {
        "name":"Acme",
        "description":"Some text here"
        "industry":"Manufacturing"
        departments: {
            "href":"/companies/1/departments"
            "count": 5
        }
        offices: {
            "href":"/companies/1/offices"
            "count": 3
        }
    }

    We don't expand the data for internal sub-resources, 
    just return the count, so client knows that some data is present.
    In some cases count may be not needed too.
GET companies/1/departments
    Returns company departments, again short info for each department
GET departments/
    Here you need to decide if it makes sense to expose 
    a list of departments or not. 
    If not - leave only companies/X/departments method.

    Note, that you can also use query string to make this 
    method "searchable", like:
        /departments?company=1 - list of all departments for company 1
        /departments?type=support - all 'support' departments for all companies
GET departments/1
    Returns department 1 data

Bằng cách này, nó trả lời hầu hết các câu hỏi của bạn - bạn "cắt" hệ thống phân cấp ngay lập tức và bạn không ràng buộc lược đồ URL của mình với cấu trúc dữ liệu nội bộ. Ví dụ: nếu chúng tôi biết ID nhân viên, bạn có muốn truy vấn nó như thế nào employees/:IDhay companies/:X/departments/:Y/employees/:IDkhông?

Về PUTvs POSTyêu cầu, từ câu hỏi của bạn rõ ràng là bạn cảm thấy các bản cập nhật phần sẽ hiệu quả hơn cho dữ liệu của bạn. Vì vậy, tôi sẽ chỉ sử dụng POSTs.

Trong thực tế, bạn thực sự muốn lưu trữ dữ liệu đọc ( GETyêu cầu) và việc cập nhật dữ liệu ít quan trọng hơn. Và cập nhật thường không thể được lưu trong bộ nhớ cache bất kể bạn yêu cầu loại nào (như nếu máy chủ tự động đặt thời gian cập nhật - nó sẽ khác nhau cho mỗi yêu cầu).

Cập nhật: liên quan đến "quá trình tư duy" đúng - vì nó dựa trên HTTP, chúng ta có thể áp dụng cách suy nghĩ thông thường khi thiết kế cấu trúc trang web. Trong trường hợp này, chúng tôi có thể có một danh sách các công ty và hiển thị một mô tả ngắn cho mỗi công ty có liên kết đến trang "xem công ty", nơi chúng tôi hiển thị chi tiết công ty và liên kết đến văn phòng / phòng ban, v.v.


5

IMHO, tôi nghĩ rằng bạn đang thiếu điểm.

Đầu tiên, hiệu năng API REST và DB không liên quan .

API REST chỉ là một giao diện , nó hoàn toàn không xác định cách bạn làm công cụ dưới mui xe. Bạn có thể ánh xạ nó tới bất kỳ cấu trúc DB nào bạn thích đằng sau nó. Vì thế:

  1. thiết kế API của bạn sao cho dễ sử dụng
  2. thiết kế cơ sở dữ liệu của bạn để nó có thể mở rộng hợp lý:
    • đảm bảo rằng bạn có các chỉ mục đúng
    • nếu bạn lưu trữ đồ vật, chỉ cần đảm bảo chúng không quá lớn.

Đó là nó.

... Và cuối cùng, điều này có vẻ như tối ưu hóa sớm. Giữ cho nó đơn giản, thử nó và thích nghi nếu cần.


2

Câu hỏi 1: Suy nghĩ của tôi có đúng không, "nơi cắt giảm thứ bậc" là một sự phân rã kỹ thuật điển hình mà tôi cần thực hiện?

Có lẽ - tôi lo lắng rằng bạn đang đi về phía sau, mặc dù.

Vì vậy, ok, khi trở lại một công ty, tôi rõ ràng không trả lại toàn bộ hệ thống phân cấp

Tôi không nghĩ đó là điều hiển nhiên cả. Bạn nên trả lại (các) đại diện của công ty phù hợp với các trường hợp sử dụng mà bạn đang hỗ trợ. Tại sao không bạn? Liệu nó thực sự có ý nghĩa rằng api phụ thuộc vào thành phần bền bỉ? Không phải là một phần của điểm mà khách hàng không cần phải tiếp xúc với lựa chọn đó trong quá trình thực hiện? Bạn sẽ bảo tồn một api bị xâm nhập khi bạn trao đổi một thành phần kiên trì cho một thành phần khác?

Điều đó nói rằng, nếu các trường hợp sử dụng của bạn không cần toàn bộ phân cấp, thì không cần phải trả lại. Trong một thế giới lý tưởng, api sẽ tạo ra các đại diện của công ty hoàn toàn phù hợp với nhu cầu trước mắt của khách hàng.

Câu hỏi 2: PUT chỉ được sử dụng cho các tài nguyên hạt mịn?

Khá nhiều - truyền đạt bản chất tự nhiên của một thay đổi bằng cách thực hiện như một cách đặt là tốt, nhưng đặc tả HTTP cho phép các tác nhân đưa ra các giả định về những gì đang thực sự xảy ra.

Lưu ý nhận xét này từ RFC 7231

Yêu cầu PUT được áp dụng cho tài nguyên đích có thể có tác dụng phụ đối với các tài nguyên khác.

Nói cách khác, bạn có thể PUT một thông báo (một "tài nguyên hạt mịn") mô tả một hiệu ứng phụ sẽ được thực thi trên tài nguyên chính (thực thể) của bạn. Bạn cần phải cẩn thận để đảm bảo việc thực hiện của bạn là bình thường.

Câu hỏi 3: Đây chỉ là một sự phân rã kỹ thuật "biết cắt giảm" hay tôi đang thiếu thứ gì?

Có lẽ. Nó có thể đang cố nói với bạn rằng các thực thể của bạn không nằm trong phạm vi chính xác.

Câu hỏi 4: Đây có phải là, hoặc nếu không, "quy trình tư duy" đúng đắn khi thiết kế dịch vụ REST và tìm kiếm sự thỏa hiệp giữa độ chi tiết tài nguyên, độ phức tạp của truy vấn và độ chói của mạng là gì?

Điều này không đúng với tôi, trong khi có vẻ như bạn đang cố gắng kết hợp chặt chẽ sơ đồ tài nguyên của mình với các thực thể của mình và để cho sự lựa chọn kiên trì của bạn điều khiển thiết kế của bạn, thay vì ngược lại.

HTTP về cơ bản là một ứng dụng tài liệu; nếu các thực thể trong miền của bạn là tài liệu, thì thật tuyệt - nhưng thực thể không phải là tài liệu, thì bạn cần phải suy nghĩ. Xem bài nói chuyện của Jim Webber : REST in Practice, đặc biệt bắt đầu từ 36m40s.

Đó là cách tiếp cận tài nguyên "hạt mịn" của bạn.


Trong câu trả lời của bạn cho câu hỏi 1, tại sao bạn nói tôi có thể sẽ đi ngược lại?
dùng3748908

Bởi vì có vẻ như bạn đang cố gắng phù hợp với các yêu cầu đối với ràng buộc lớp liên tục, thay vì cách khác.
VoiceOfUnreason

2

Nói chung, bạn không muốn bất kỳ chi tiết triển khai nào được hiển thị trong API. Cả hai câu trả lời của msw và VoiceofUnreason đều truyền đạt điều đó, vì vậy điều quan trọng là phải tiếp tục.

Hãy ghi nhớ nguyên tắc ít ngạc nhiên nhất là khi bạn lo lắng về sự bình tĩnh. Hãy xem một số ý kiến ​​trong bài viết bạn đã đăng ( https://stormpath.com/blog/put-or-post/ ); có rất nhiều sự bất đồng ở đó về cách bài báo trình bày sự bình tĩnh. Ý tưởng lớn mà tôi sẽ lấy ra từ bài viết là "Yêu cầu đặt giống hệt nhau sẽ gây ra kết quả giống hệt nhau". Tức là Nếu bạn PUT một bản cập nhật cho tên của công ty, tên của công ty sẽ thay đổi và không có gì khác thay đổi cho công ty đó do kết quả của PUT đó. Yêu cầu tương tự 5 phút sau sẽ có hiệu lực tương tự.

Một câu hỏi thú vị để suy nghĩ (xem bình luận của gtrevg trong bài viết): mọi yêu cầu PUT, bao gồm cả bản cập nhật đầy đủ, sẽ sửa đổi dateUpdated ngay cả khi khách hàng không chỉ định nó. Điều đó có làm cho bất kỳ yêu cầu PUT nào vi phạm tính không minh bạch không?

Vì vậy, trở lại API. Những điều chung để suy nghĩ:

  • Cần tránh các chi tiết triển khai trong API
  • Nếu việc triển khai thay đổi, API của bạn vẫn phải trực quan và dễ sử dụng
  • Tài liệu rất quan trọng
  • Cố gắng không làm cong API để có được cải tiến hiệu suất

1
bỏ qua một bên : sự bình tĩnh bị ràng buộc theo ngữ cảnh. Ví dụ, các quy trình Ghi nhật ký và Kiểm toán có thể được kích hoạt bên trong PUT và các hành động này là không bình thường. Nhưng đây là những chi tiết triển khai nội bộ và không tác động đến các đại diện được thể hiện thông qua sự trừu tượng hóa dịch vụ; do đó, theo như API có liên quan, PUT idempotent.
K. Alan Bates

0

Đối với Q1 của bạn về nơi cắt giảm các quyết định kỹ thuật, làm thế nào về việc chọn Id duy nhất của một thực thể theo cách khác sẽ cung cấp cho bạn các chi tiết cần thiết về phụ trợ? Ví dụ: "công ty / 1 / bộ phận / 1" sẽ có một Mã định danh duy nhất (hoặc chúng tôi có thể có một đại diện giống nhau) để cung cấp cho bạn hệ thống phân cấp, bạn có thể sử dụng phân cấp đó.

Đối với quý 3 trên PUT với thông tin đầy đủ, bạn có thể gắn cờ các trường đã được cập nhật và gửi thông tin siêu dữ liệu bổ sung đó đến máy chủ để bạn xem xét và cập nhật các trường đó một mình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.