2016-02-29 38 views
20

Trong dự án của tôi sử dụng phương pháp DDD.API còn lại và DDD

Dự án có Thỏa thuận tổng thể (thực thể). Tổng hợp này có nhiều trường hợp sử dụng.

Đối với tổng hợp này, tôi cần tạo api còn lại.

Với tiêu chuẩn: tạo và xóa không có vấn đề gì.

1) CreateDealUseCase (tên, giá và nhiều thông số khác);

POST /rest/{version}/deals/ 
{ 
    'name': 'deal123', 
    'price': 1234; 
    'etc': 'etc' 
} 

2) DeleteDealUseCase (id)

DELETE /rest/{version}/deals/{id} 

Nhưng phải làm gì với phần còn lại của trường hợp sử dụng?

  • HoldDealUseCase (id, lý do);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase (id và nhiều thông số khác);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, lý do);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • v.v. (tổng số 20 trường hợp sử dụng) ...

Giải pháp là gì?

1) Sử dụng động từ:

PUT /rest/{version}/deals/{id}/hold 
{ 
    'reason': 'test' 
} 

Nhưng! Động từ không thể được sử dụng trong url (trong lý thuyết REST).

2) Sử dụng hoàn thành trạng thái (mà sẽ được sau khi các trường hợp sử dụng):

PUT /rest/{version}/deals/{id}/holded 
{ 
    'reason': 'test' 
} 

Cá nhân đối với tôi nó trông xấu xí. Có lẽ tôi sai?

3) Sử dụng 1 yêu cầu PUT cho tất cả các hoạt động:

PUT /rest/{version}/deals/{id} 
{ 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

PUT /rest/{version}/deals/{id} 
{ 
    'action': 'UnholdDeal', 
    'params': {} 
} 

Thật khó để xử lý trong backend. Hơn nữa, rất khó để ghi lại tài liệu. Vì 1 hành động có nhiều biến thể yêu cầu khác nhau, từ đó đã phụ thuộc vào các phản hồi cụ thể.

Tất cả các giải pháp đều có những hạn chế đáng kể.

Tôi đã đọc nhiều bài viết về REST trên internet. Ở khắp mọi nơi chỉ có một lý thuyết, làm thế nào để ở đây với vấn đề cụ thể của tôi?

+1

Tôi không muốn nêu rõ câu trả lời dưới đây vì vậy có lẽ những người khác có thể đưa ra ý kiến ​​của họ trong trường hợp đó là một ý tưởng tồi tệ. Làm thế nào về: '/ phần còn lại/{phiên bản}/dealsheld /', '/ phần còn lại/{phiên bản}/dealscompleted/{id}', vv Vì một trong những sẽ cần phải biết được nhà nước bạn đang đối phó với trong bất kỳ sự kiện. Một kế hoạch như vậy có hợp lý không? –

Trả lời

16

Tôi đã đọc nhiều bài viết về REST trên internet.

Dựa trên những gì tôi thấy ở đây, bạn thực sự cần phải xem ít nhất một trong các cuộc đàm phán Jim Webber về REST và DDD

Nhưng những gì để làm gì với các trường hợp sử dụng còn lại?

Bỏ qua API trong giây lát - bạn sẽ làm thế nào với biểu mẫu HTML?

Bạn có thể có một trang web trình bày một đại diện của Deal, với một loạt các liên kết trên đó. Một liên kết sẽ đưa bạn đến biểu mẫu HoldDeal và một liên kết khác sẽ đưa bạn đến biểu mẫu Thay đổi, v.v. Mỗi biểu mẫu sẽ có không hoặc nhiều trường để điền vào và mỗi biểu mẫu sẽ đăng lên một số tài nguyên để cập nhật mô hình miền.

Tất cả chúng có được đăng lên cùng một tài nguyên không? Có lẽ, có lẽ không. Tất cả chúng đều có cùng loại phương tiện, vì vậy nếu tất cả chúng đều được đăng lên cùng một điểm cuối, bạn sẽ phải giải mã nội dung ở phía bên kia.

Với cách tiếp cận đó, làm cách nào để bạn triển khai hệ thống của mình? Vâng, các loại phương tiện truyền thông muốn được json, dựa trên các ví dụ của bạn, nhưng có thực sự không phải là bất cứ điều gì sai trái với phần còn lại của nó.

1) Sử dụng động từ:

Đó là tốt.

Nhưng! Động từ không thể được sử dụng trong url (trong lý thuyết REST).

Um ... no. REST không quan tâm đến chính tả của số nhận dạng tài nguyên của bạn. Có một loạt các thực hành tốt nhất của URI cho rằng động từ là xấu - đó là sự thật - nhưng đó không phải là thứ gì đó theo sau từ REST.

Nhưng nếu mọi người đang quá cầu kỳ, bạn đặt tên điểm cuối cho lệnh thay vì động từ. (ví dụ: "giữ" không phải là động từ, đó là trường hợp sử dụng).

Sử dụng 1 yêu cầu PUT cho tất cả các hoạt động:

Thành thực mà nói, đó là một không phải là xấu cả. Tuy nhiên, bạn sẽ không muốn chia sẻ uri (vì cách mà phương thức PUT được chỉ định), nhưng sử dụng một khuôn mẫu nơi mà các máy khách có thể chỉ định một mã định danh duy nhất.

Đây là thịt: bạn đang xây dựng một api trên đầu trang của các động từ HTTP và HTTP. HTTP được thiết kế cho chuyển tài liệu. Khách hàng cung cấp cho bạn một tài liệu, mô tả một thay đổi được yêu cầu trong mô hình miền của bạn và bạn áp dụng thay đổi cho tên miền (hoặc không) và trả về một tài liệu khác mô tả trạng thái mới.

Vay từ từ vựng CQRS trong giây lát, bạn đang đăng các lệnh để cập nhật mô hình miền của mình.

PUT /commands/{commandId} 
{ 
    'deal' : dealId 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

Xác minh - bạn đang đặt một lệnh cụ thể (lệnh có Id cụ thể) vào hàng đợi lệnh, là tập hợp.

PUT /rest/{version}/deals/{dealId}/commands/{commandId} 
{ 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

Vâng, cũng tốt.

Hãy xem xét RESTBucks khác. Đó là một giao thức quán cà phê, nhưng tất cả các api chỉ là đi qua các tài liệu nhỏ xung quanh để thúc đẩy máy nhà nước.

+2

Tôi dường như bạn đã phát minh ra các cuộc gọi thủ tục từ xa dựa trên REST. – xfg

+1

Nhưng nếu bạn không muốn xây dựng 20 thiết bị đầu cuối có thể tuân theo hành vi của mô hình miền? 20 điểm cuối cực kỳ khó duy trì.Điều gì sẽ xảy ra nếu bạn có một điểm cuối và một lớp bổ sung giữa lớp ứng dụng và lớp miền sẽ so sánh và xử lý dữ liệu đã đăng để kích hoạt hành vi miền thích hợp? – mko

7

Thiết kế api còn lại của bạn độc lập với lớp miền.

Một trong những khái niệm chính về thiết kế điều khiển miền là khớp nối thấp giữa các lớp phần mềm khác nhau của bạn. Vì vậy, khi bạn thiết kế api còn lại của bạn, bạn nghĩ về api nghỉ ngơi tốt nhất bạn có thể có. Sau đó, đó là vai trò của lớp ứng dụng để gọi các đối tượng miền để thực hiện yêu cầu sử dụng.

Tôi không thể thiết kế phần còn lại cho bạn vì tôi không biết bạn đang cố gắng làm gì, nhưng đây là một số ý tưởng.

Như tôi đã hiểu, bạn có tài nguyên Giao dịch. Như bạn nói, tạo/xóa dễ dàng:

  • POST/nghỉ ngơi/{version}/đề
  • DELETE/nghỉ ngơi/{version}/giao dịch/{id}.

Sau đó, bạn muốn "giữ" giao dịch. Tôi không biết điều đó có nghĩa là gì, bạn phải suy nghĩ về những gì nó thay đổi trong tài nguyên "Deal". Nó có thay đổi một thuộc tính không? nếu có, thì bạn chỉ cần sửa đổi tài nguyên Thỏa thuận.

PUT/nghỉ ngơi/{version}/giao dịch/{id}

{ 
    ... 
    held: true, 
    holdReason: "something", 
    ... 
} 

Liệu nó thêm một cái gì đó? Bạn có thể nắm giữ một số giao dịch không? Âm thanh với tôi rằng "giữ" là một danh từ. Nếu nó xấu xí, hãy tìm một danh từ tốt hơn.

POST/nghỉ ngơi/{version}/giao dịch/{id}/giữ

{ 
    reason: "something" 
} 

một giải pháp khác: quên lý thuyết REST. Nếu bạn nghĩ rằng api của bạn sẽ rõ ràng hơn, hiệu quả hơn, đơn giản hơn với việc sử dụng các động từ trong url, thì bằng mọi cách, hãy làm điều đó. Bạn có thể tìm cách tránh nó, nhưng nếu bạn không thể, đừng làm điều xấu xí chỉ vì đó là tiêu chuẩn.

Nhìn vào twitter's api: nhiều nhà phát triển nói rằng twitter có API được thiết kế tốt. Tadaa, nó dùng động từ! Ai quan tâm, miễn là nó mát mẻ và dễ sử dụng?

tôi không thể thiết kế api cho bạn, bạn là người duy nhất biết trường hợp sử dụng của bạn, nhưng tôi sẽ nói một lần nữa tôi hai lời khuyên:

  • Thiết kế các api phần còn lại của chính nó, và sau đó sử dụng lớp ứng dụng để gọi các đối tượng miền thích hợp theo đúng thứ tự. Đó chính xác là những gì mà lớp ứng dụng ở đây.
  • Không tuân theo các tiêu chuẩn và lý thuyết một cách mù quáng. Có, bạn nên cố gắng tuân theo các thực hành và tiêu chuẩn tốt nhất có thể, nhưng nếu bạn không thể để chúng lại sau (sau khi xem xét cẩn thận tất nhiên)
+1

Thiết kế dựa trên miền là về miền. Khách hàng API cũng nên được thiết kế với tên miền. Nếu không, bạn sẽ mất hầu hết các lợi ích của DDD. –

+0

Vâng, nhưng điều đó không có nghĩa là bạn nên phơi bày tất cả sự phức tạp của miền với người tiêu dùng API. API có thể đưa ra một tập hợp con các chức năng của lớp miền chẳng hạn. – Kaidjin

+0

Bạn không nên để lộ logic của trình xử lý lệnh hoặc nội bộ khác. Nhưng nó nói về các lệnh tổng hợp. Nó không phải là tất cả sự phức tạp, nó là hình dạng công khai của miền. –

0

Sử dụng hoặc không sử dụng động từ trong URL REST là mâu thuẫn môn học. Tuy nhiên, nếu bạn không muốn sử dụng động từ, bạn luôn có thể thực hiện PUT thành /rest/{version}/deals và thêm thông số truy vấn /rest/{version}/deals/{id}?action=hold. Theo cùng một mẫu, bạn có thể làm Action một phần của phần thân yêu cầu PUT của bạn.

Lớp ứng dụng của bạn sau đó sẽ tạo thành lệnh cụ thể hơn và gửi nó để logic miền của bạn sẽ vẫn bị cô lập trong trình xử lý lệnh riêng biệt.

+0

PUT đó phải là POST nếu bạn nói rằng nó nên thực hiện một hành động trên một số tài nguyên hiện có. – roarsneer

+0

@ bkhl nói ai? 'PUT' phải là idempotent, tại sao nó không thể áp dụng một sửa đổi nếu kết quả sẽ giống nhau? –

+0

PUT trong REST là để cập nhật tài nguyên bằng cách tải lên một biểu diễn mới. http://restcookbook.com/HTTP%20Methods/put-vs-post/ có một lời giải thích ngắn – roarsneer

0

Tôi tách riêng các ca sử dụng (UC) trong 2 nhóm: lệnh và truy vấn (CQRS) và tôi có 2 bộ điều khiển REST (một cho lệnh và một cho truy vấn). Các tài nguyên REST không phải là các đối tượng mô hình để thực hiện các hoạt động CRUD trên chúng như là kết quả của POST/GET/PUT/DELETE. Tài nguyên có thể là bất kỳ đối tượng nào bạn muốn. Thật vậy trong DDD bạn không nên để lộ mô hình miền cho các bộ điều khiển.

(1) RestApiCommandController: Một phương pháp cho mỗi trường hợp sử dụng lệnh. Tài nguyên REST trong URI là tên lớp lệnh. Phương thức luôn là POST, bởi vì bạn tạo lệnh, và sau đó bạn thực hiện nó thông qua một lệnh bus (một trình trung gian trong trường hợp của tôi). Cơ thể yêu cầu là một đối tượng JSON ánh xạ các thuộc tính lệnh (các arg của UC).

Ví dụ: http://localhost:8181/command/asignTaskCommand/

@RestController 
@RequestMapping("/command") 
public class RestApiCommandController { 

private final Mediator mediator;  

@Autowired 
public RestApiCommandController (Mediator mediator) { 
    this.mediator = mediator; 
}  

@RequestMapping(value = "/asignTaskCommand/", method = RequestMethod.POST) 
public ResponseEntity<?> asignTask (@RequestBody AsignTaskCommand asignTaskCommand) {  
    this.mediator.execute (asigTaskCommand); 
    return new ResponseEntity (HttpStatus.OK); 
} 

(2) RestApiQueryController: Một phương pháp cho mỗi trường hợp sử dụng truy vấn. Ở đây tài nguyên REST trong URI là đối tượng DTO mà truy vấn trả về (làm phần tử của một bộ sưu tập hoặc chỉ một mình). Phương thức luôn là GET và các tham số của truy vấn UC là các tham số trong URI.

Ví dụ: http://localhost:8181/query/asignedTask/1

@RestController 
@RequestMapping("/query") 
public class RestApiQueryController { 

private final Mediator mediator;  

@Autowired 
public RestApiQueryController (Mediator mediator) { 
    this.mediator = mediator; 
}  

@RequestMapping(value = "/asignedTask/{employeeId}", method = RequestMethod.GET) 
public ResponseEntity<List<AsignedTask>> asignedTasksToEmployee (@PathVariable("employeeId") String employeeId) { 

    AsignedTasksQuery asignedTasksQuery = new AsignedTasksQuery (employeeId); 
    List<AsignedTask> result = mediator.executeQuery (asignedTasksQuery); 
    if (result==null || result.isEmpty()) { 
     return new ResponseEntity (HttpStatus.NOT_FOUND); 
    } 
    return new ResponseEntity<List<AsignedTask>>(result, HttpStatus.OK); 
} 

LƯU Ý: Mediator thuộc lớp ứng dụng DDD. Đó là ranh giới UC, nó tìm kiếm lệnh/truy vấn và thực hiện dịch vụ ứng dụng thích hợp.

Các vấn đề liên quan