2016-02-29 8 views
20

В моем проекте используется методология DDD.Rest API и DDD

Проект имеет совокупность (сущность) Сделка. Этот агрегат имеет много вариантов использования.

Для этого агрегата мне нужно создать rest api.

Со стандартом: создавать и удалять без проблем.

1) CreateDealUseCase (имя, цена и многие другие параметры);

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

2) DeleteDealUseCase (ID)

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

Но что делать с остальными вариантами использования?

  • HoldDealUseCase (id, reason);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase (id и многие другие параметры);
  • ОтменитьDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, reason);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • и т.д. (всего 20 случаев использования) ...

Каковы решения?

1) Используйте глаголы:

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

Но! Глаголы не могут использоваться в URL-адресе (в теории REST).

2) Используйте завершенная состояние (который будет после случая использования):

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

Лично для меня это выглядит некрасиво. Может, я ошибаюсь?

3) Используйте 1 запрос PUT для всех операций:

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

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

трудно обрабатывать во внутреннем интерфейсе. Кроме того, документ трудно документировать. Поскольку 1 действие имеет много разных вариантов запросов, из которых уже зависит от конкретных ответов.

Все решения имеют существенные недостатки.

Я прочитал много статей о REST в Интернете. Везде только теория, как быть здесь с моей конкретной проблемой?

+1

Я не хочу заявлять следующее в качестве ответа, поэтому, возможно, другие могут дать свое мнение в том случае, если это ужасная идея. Как насчет: '/ rest/{version}/dealheld /', '/ rest/{version}/dealscompleted/{id}' и т. Д. Так как нужно знать, с каким состоянием вы имеете дело в любом случае. Может ли такая схема иметь смысл? –

ответ

16

Я прочитал много статей о REST в Интернете.

Основываясь на том, что я вижу здесь, что вам действительно нужно смотреть по крайней мере один из переговоров Джима Уэббера на REST и DDD

Но что делать с остальными вариантами использования?

Игнорировать API на мгновение - как вы это сделаете с помощью HTML-форм?

Возможно, у вас есть веб-страница, представляющая собой представление сделки, с кучей ссылок на ней. Одна ссылка приведет вас к форме HoldDeal, а другая ссылка перенесет вас в форму ChangePrice и т. Д. В каждой из этих форм будет заполнено нулевое или большее число полей, и формы будут помещаться на какой-либо ресурс для обновления модели домена.

Все ли они отправляют на тот же ресурс? Возможно, возможно нет. Все они будут иметь один и тот же тип носителя, поэтому, если бы все они отправлялись в одну и ту же конечную точку веб-сайта, вам пришлось бы декодировать контент с другой стороны.

Учитывая этот подход, как вы реализуете свою систему? Ну, медиа-тип хочет быть json, основываясь на ваших примерах, но на самом деле нет ничего плохого в остальном.

1) Используйте глаголы:

Это нормально.

Но! Глаголы не могут использоваться в URL-адресе (в теории REST).

Um ... no. REST не заботится о написании ваших идентификаторов ресурсов. Есть куча лучших практик URI, которые утверждают, что глаголы плохие - это правда, но это не то, что следует из REST.

Но если люди так суетливы, вы называете конечную точку для команды вместо глагола. (т. е. «hold» не является глаголом, это вариант использования).

Используйте 1 запрос PUT для всех операций:

Честно говоря, что один не плох. Вы не захотите делиться uri хотя (из-за того, как указан метод PUT), но используйте шаблон, в котором клиенты могут указывать уникальный идентификатор.

Вот мясо: вы строите api поверх HTTP и HTTP-глаголов. HTTP предназначен для перевод документа. Клиент предоставляет вам документ, описывающий запрошенное изменение в вашей модели домена, и вы применяете изменение к домену (или нет) и возвращаете другой документ, описывающий новое состояние.

Заимствование из словаря CQRS на мгновение, вы отправляете команды для обновления своей модели домена.

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

Обоснование - вы ставите определенную команду (команды с определенным Id) в очереди команд, который представляет собой набор.

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

Да, это прекрасно.

Посмотрите еще раз на RESTBucks. Это протокол кофейни, но все api просто пропускают небольшие документы, чтобы продвинуть машину состояния.

+2

Кажется, вы изобрели удаленные вызовы процедур на основе REST. – xfg

+1

Но что, если вы не хотите строить 20 конечных точек, которые будут следовать за поведением модели домена? 20 очень сложно поддерживать.Что делать, если у вас есть одна конечная точка и дополнительный уровень между областями приложений и домена, которые будут сравнивать и обрабатывать размещенные данные, чтобы вызвать правильное поведение домена? – mko

7

Создайте свой отдых api независимо от уровня домена.

Одна из ключевых концепций ведомого домена - Низкая связь между различными слоями программного обеспечения. Итак, когда вы разрабатываете свой отдых api, вы думаете о лучшем отдыхе, который у вас может быть. Затем роль прикладного уровня - вызов объектов домена для выполнения требуемого варианта использования.

Я не могу создать для вас ваш отдых api, потому что я не знаю, что вы пытаетесь сделать, но вот некоторые идеи.

Как я понимаю, у вас есть ресурс Deal. Как вы сказали, создание/удаление легко:

  • POST/отдых/{версия}/сделок
  • УДАЛИТЬ/отдых/{версия}/предложения/{ID}.

Затем вы хотите «провести» сделку. Я не знаю, что это значит, вы должны подумать о том, что он изменил в ресурсе «Сделка». Изменяет ли атрибут? если да, то вы просто изменяете ресурс Deal.

PUT/отдых/{версия}/предложения/{идентификатор}

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

ли это что-то добавить? Можете ли вы провести несколько сделок по сделке? Мне кажется, что «держать» является существительным. Если это уродливо, найдите лучшее существительное.

POST/отдых/{версия}/предложения/{ID}/держит

{ 
    reason: "something" 
} 

другое решение: забыть теорию REST. Если вы считаете, что ваш api будет более понятным, более эффективным, проще с использованием глаголов в URL-адресе, то, во что бы то ни стало, сделайте это. Вы, вероятно, можете найти способ избежать этого, но если вы не можете, не делайте что-то уродливое только потому, что это норма.

Посмотрите на twitter's api: многие разработчики говорят, что у twitter есть хорошо разработанный API. Тадаа, он использует глаголы! Кому это важно, если это круто и легко использовать?

Я не могу создать свой API для вас, вы единственный, кто знает прецеденты, но я скажу еще раз мои два советы:

  • Дизайн остальное апи сам по себе, и затем используйте прикладной уровень для вызова соответствующих объектов домена в правильном порядке. Именно для этого и предназначен прикладной уровень.
  • Не следите за нормами и теориями вслепую. Да, вы должны стараться как можно больше следовать передовым методам и нормам, но если вы не можете их оставить (после тщательного рассмотрения курса)
+1

Домен-ориентированный дизайн о домене. Клиенты API также должны разрабатываться с учетом домена. В противном случае вы потеряете большую часть преимуществ DDD. –

+0

Да, но это не значит, что вы должны подвергать всю сложность вашего домена потребителям API. Например, API может отображать подмножество функциональных возможностей уровня домена. – Kaidjin

+0

Вы не должны раскрывать логику обработчиков команд или других внутренних элементов. Но речь идет о совокупных командах. Это не вся сложность, это общественный облик домена. –

0

Использование или неприменение глаголов в URL REST является противоречивым предмет. Однако, если вы не хотите использовать глаголы, вы всегда можете сделать PUT до /rest/{version}/deals и добавить параметр запроса /rest/{version}/deals/{id}?action=hold. Следуя той же схеме, вы можете сделать Action часть вашего тела запроса PUT.

Затем ваш прикладной уровень формирует более конкретную команду и отправляет ее, чтобы ваша логика домена оставалась изолированной в отдельных обработчиках команд.

+0

Этот PUT должен быть POST, если вы говорите, что он должен выполнить действие над уже существующим ресурсом. – roarsneer

+0

@bkhl говорит, кто? 'PUT' должен быть идемпотентным, почему он не может применить модификацию, если результат будет таким же? –

+0

PUT in REST предназначен для обновления ресурса путем загрузки нового представления. http://restcookbook.com/HTTP%20Methods/put-vs-post/ имеет короткое объяснение – roarsneer

0

Я разделяю варианты использования (UC) в двух группах: команды и запросы (CQRS), и у меня есть 2 контроллера REST (один для команд и другой для запросов). Ресурсы REST не должны быть образцовыми объектами для выполнения на них CRUD-операций в результате POST/GET/PUT/DELETE. Ресурсы могут быть любыми объектами, которые вы хотите. Действительно, в DDD вы не должны подвергать модель домена контроллерам.

(1) RestApiCommandController: Один метод для использования в команде. Ресурс REST в URI - это имя класса команд. Метод всегда POST, потому что вы создаете команду, а затем выполняете ее через командную шину (в моем случае - медиатор). Тело запроса - объект JSON, который отображает свойства команды (args UC).

Например: 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: Один из способов использования в запросе случае. Здесь ресурс REST в URI - это объект DTO, возвращаемый запросом (как элемент коллекции, или только один). Метод всегда является GET, а параметры UC запроса являются параметрами в URI.

Например: 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); 
} 

Примечание: Посредник относится к прикладному уровню DDD. Это граница UC, она ищет команду/запрос и выполняет соответствующую службу приложений.