API CRUD: Làm thế nào để bạn chỉ định các trường cần cập nhật?


9

Giả sử bạn có một số loại cấu trúc dữ liệu, được duy trì trong một số loại cơ sở dữ liệu. Để đơn giản, hãy gọi cấu trúc dữ liệu này Person. Bây giờ bạn có nhiệm vụ thiết kế API CRUD, cho phép các ứng dụng khác tạo, đọc, cập nhật và xóa Persons. Để đơn giản, hãy giả sử rằng API này được truy cập thông qua một số loại dịch vụ web.

Đối với các phần C, R và D của CRUD, thiết kế rất đơn giản. Tôi sẽ sử dụng ký hiệu chức năng giống như C # - việc triển khai có thể là SOAP, REST / JSON hoặc một cái gì đó khác:

class Person {
    string Name;
    DateTime? DateOfBirth;
    ...
}

Identifier CreatePerson(Person);
Person GetPerson(Identifier);
void DeletePerson(Identifier);

Còn cập nhật thì sao? Điều tự nhiên phải làm là

void UpdatePerson(Identifier, Person);

nhưng làm thế nào bạn sẽ xác định được lĩnh vực Personđể cập nhật?


Các giải pháp mà tôi có thể đưa ra:

  • Bạn luôn có thể yêu cầu một Người hoàn thành được thông qua, tức là khách hàng sẽ làm một cái gì đó như thế này để cập nhật ngày sinh:

    p = GetPerson(id);
    p.DateOfBirth = ...;
    UpdatePerson(id, p);
    

    Tuy nhiên, điều đó sẽ yêu cầu một số loại nhất quán giao dịch hoặc khóa giữa Nhận và Cập nhật; mặt khác, bạn có thể ghi đè lên một số thay đổi khác được thực hiện song song bởi một số khách hàng khác. Điều này sẽ làm cho API phức tạp hơn nhiều. Ngoài ra, nó dễ bị lỗi, vì mã giả sau đây (giả sử ngôn ngữ máy khách có hỗ trợ JSON)

    UpdatePerson(id, { "DateOfBirth": "2015-01-01" });
    

    - có vẻ đúng - sẽ không chỉ thay đổi DateOfBirth mà còn đặt lại tất cả các trường khác thành null.

  • Bạn có thể bỏ qua tất cả các lĩnh vực đó null. Tuy nhiên, làm thế nào bạn sẽ tạo ra sự khác biệt giữa việc không thay đổi DateOfBirthcố tình thay đổi nó thành null ?

  • Thay đổi chữ ký thành void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate).

  • Thay đổi chữ ký thành void UpdatePerson(Identifier, ListOfFieldValuePairs).

  • Sử dụng một số tính năng của giao thức truyền: Ví dụ: bạn có thể bỏ qua tất cả các trường không có trong biểu diễn JSON của Người. Tuy nhiên, điều đó thường yêu cầu tự phân tích JSON và không thể sử dụng các tính năng tích hợp trong thư viện của bạn (ví dụ WCF).

Không có giải pháp nào có vẻ thực sự thanh lịch với tôi. Chắc chắn, đây là một vấn đề phổ biến, vậy giải pháp thực hành tốt nhất được mọi người sử dụng là gì?


Tại sao không phải là một phần định danh của người? Đối với các Persontrường hợp mới được tạo mà vẫn không được duy trì và trong trường hợp định danh được quyết định như là một phần của cơ chế lưu giữ lâu dài, chỉ cần để nó thành null. Đối với câu trả lời, JPA sử dụng số phiên bản; nếu bạn đọc phiên bản 23, khi bạn cập nhật mục nếu phiên bản trong DB là 24 thì việc ghi không thành công.
SJuan76

Cho phép và giao tiếp cả PUTPATCHphương pháp. Khi sử dụng PATCH, chỉ nên thay thế phím gửi, với PUTtoàn bộ đối tượng được thay thế.
Lode

Câu trả lời:


8

Nếu bạn không theo dõi các thay đổi theo yêu cầu đối với đối tượng này (ví dụ: "Người dùng John đã thay đổi tên và ngày sinh"), đơn giản nhất sẽ là ghi đè toàn bộ đối tượng trong DB bằng một đối tượng bạn nhận được từ người tiêu dùng. Cách tiếp cận này sẽ liên quan đến việc dữ liệu được gửi qua dây nhiều hơn một chút, nhưng bạn đang tránh đọc trước khi cập nhật.

Nếu bạn có yêu cầu theo dõi hoạt động. Thế giới của bạn phức tạp hơn nhiều và bạn sẽ cần thiết kế cách lưu trữ thông tin về các hành động CRUD và cách chặn chúng. Đó là thế giới bạn không muốn lao vào nếu bạn không có yêu cầu như vậy.

Theo giá trị ghi đè bằng các giao dịch riêng biệt, tôi sẽ đề nghị thực hiện nghiên cứu xung quanh việc khóa lạc quanbi quan . Họ giảm thiểu kịch bản phổ biến này:

  1. Đối tượng được đọc bởi user1
  2. Đối tượng được đọc bởi user2
  3. Đối tượng được viết bởi user1
  4. Đối tượng được viết bởi user2 và ghi đè thay đổi bởi user1

Mỗi người dùng có giao dịch khác nhau, do đó, SQL tiêu chuẩn với điều này. Phổ biến nhất là khóa lạc quan (cũng được đề cập bởi @ SJuan76 trong nhận xét về các phiên bản). Phiên bản của bạn bản ghi của bạn trong DB và trong khi viết, trước tiên bạn hãy xem DB nếu các phiên bản khớp. Nếu các phiên bản không khớp, bạn sẽ biết ai đó đã cập nhật đối tượng trong thời gian này và bạn cần phản hồi với thông báo lỗi cho người tiêu dùng về tình huống này. Có, bạn phải hiển thị tình huống này cho người dùng.

Lưu ý rằng bạn cần đọc bản ghi thực tế từ DB trước khi viết nó (để so sánh phiên bản khóa tối ưu), do đó, việc triển khai logic delta (chỉ ghi giá trị thay đổi) có thể không yêu cầu truy vấn đọc thêm trước khi ghi.

Đưa vào logic delta phụ thuộc nhiều vào hợp đồng với người tiêu dùng, nhưng lưu ý rằng người tiêu dùng dễ nhất là xây dựng toàn bộ trọng tải thay vì delta.


2

Chúng tôi có một API PHP tại nơi làm việc. Để cập nhật nếu một trường không được gửi trong đối tượng JSON, nó sẽ được đặt thành NULL. Sau đó, nó chuyển mọi thứ đến thủ tục lưu trữ. Quy trình được lưu trữ cố gắng cập nhật mọi trường với trường = IFNULL (đầu vào, trường). Vì vậy, nếu chỉ có 1 trường trong đối tượng JSON thì chỉ có trường đó được cập nhật. Để làm trống một cách rõ ràng một trường đã đặt, chúng ta phải có trường = '', DB sau đó cập nhật trường bằng chuỗi rỗng hoặc giá trị mặc định của cột đó.


3
Làm thế nào để bạn cố tình đặt một trường thành null mà chưa phải là null?
Robert Harvey

Tất cả các trường được đặt KHÔNG NULL, vì vậy mặc định các trường CHAR nhận '' và tất cả các trường số nguyên nhận 0.
Jared Bernacchi

1

Chỉ định danh sách các trường được cập nhật trong Chuỗi truy vấn.

PUT /resource/:id?fields=name,address,dob Body { //resource body }

Thực hiện hợp nhất dữ liệu được lưu trữ với mô hình từ thân yêu cầu:

private ResourceModel MergeResourceModel(ResourceModel original, ResourceModel updated, List<string> fields)
{
    var comparer = new FieldComparer();

    foreach (
            var item in
            typeof (ResourceModel).GetProperties()
                    .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof (JsonIgnoreAttribute))))
    {
        if (fields.Contains(item.Name, comparer))
        {
            var property = typeof (ResourceModel).GetProperty(item.Name);
            property.SetValue(original, property.GetValue(updated));
        }
    }

    return original;
}
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.