Подтвердить что ты не робот

Rest API и DDD

В моем проекте используется методология 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, причина);
  • UnholdDealUseCase (ID);
  • CompleteDealUseCase (id и многие другие параметры);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, причина);
  • 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 в Интернете. Везде только теория, как быть здесь с моей конкретной проблемой?

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

Это хорошо.

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

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

Но если люди так суетливы, вы называете конечную точку для команды вместо глагола. (т.е. "удержание" не является глаголом, это прецедент).

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

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

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

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

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

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

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

Да, это тоже хорошо.

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

Ответ 2

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

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

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

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

  • POST/rest/{version}/deals
  • DELETE/rest/{version}/deals/{id}.

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

PUT/rest/{version}/deals/{id}

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

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

POST/rest/{version}/deals/{id}/содержит

{
    reason: "something"
}

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

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

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

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

Ответ 3

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

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

Ответ 4

Я разделяю варианты использования (UC) в 2 группах: команды и запросы (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, она ищет команду/запрос и выполняет соответствующую службу приложений.