software issues issue example create all api rest domain-driven-design

example - jira rest api get all issues



Rest API y DDD (4)

He leído muchos artículos sobre el REST en Internet.

Basado en lo que veo aquí, realmente necesita ver al menos una de las charlas de Jim Webber sobre REST y DDD

Pero, ¿qué hacer con el resto de los casos de uso?

Ignore la API por un momento. ¿Cómo lo haría con formularios HTML?

Probablemente tengas una página web que presenta una representación de Deal, con un montón de enlaces. Un enlace lo llevaría al formulario HoldDeal, y otro vínculo lo llevaría al formulario ChangePrice, y así sucesivamente. Cada uno de esos formularios tendría cero o más campos para completar, y cada formulario se publicaría en algún recurso para actualizar el modelo de dominio.

¿Todos publicarían en el mismo recurso? Quizás, quizás no. Todos tendrían el mismo tipo de medio, por lo que si todos estuvieran publicando en el mismo punto final web, tendrían que decodificar el contenido del otro lado.

Dado ese enfoque, ¿cómo implementa su sistema? Bueno, el tipo de medios quiere ser json, basado en tus ejemplos, pero realmente no hay nada de malo con el resto.

1) Usar verbos:

Esta bien.

¡Pero! Los verbos no se pueden usar en la url (en la teoría REST).

Mmm no. A REST no le importa la ortografía de los identificadores de recursos. Hay un montón de buenas prácticas de URI que afirman que los verbos son malos, eso es cierto, pero eso no es algo que se desprenda de REST.

Pero si las personas están siendo tan exigentes, nombre el punto final para el comando en lugar del verbo. (es decir: "mantener" no es un verbo, es un caso de uso).

Utilice la solicitud 1 PUT para todas las operaciones:

Honestamente, ese tampoco está mal. Sin embargo, no querrá compartir el uri (debido a la forma en que se especifica el método PUT), pero use una plantilla donde los clientes puedan especificar un identificador único.

Aquí está la carne: estás construyendo una API sobre los verbos HTTP y HTTP. HTTP está diseñado para la transferencia de documentos . El cliente le proporciona un documento que describe un cambio solicitado en su modelo de dominio, y aplica el cambio al dominio (o no) y devuelve otro documento que describe el nuevo estado.

Tomando prestado del vocabulario de CQRS por un momento, está publicando comandos para actualizar su modelo de dominio.

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

Justificación: está poniendo un comando específico (un comando con un Id específico) en la cola de comandos, que es una colección.

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

Sí, está bien también.

Eche un vistazo a RESTBucks. Es un protocolo de cafetería, pero toda la API está pasando pequeños documentos para avanzar la máquina de estado.

En mi proyecto usando la metodología DDD.

El proyecto tiene la oferta total (entidad). Este agregado tiene muchos casos de uso.

Para este agregado, necesito crear una API de descanso.

Con estándar: crea y elimina ningún problema.

1) CreateDealUseCase (nombre, precio y muchos otros parámetros);

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

2) DeleteDealUseCase (id)

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

Pero, ¿qué hacer con el resto de los casos de uso?

  • HoldDealUseCase (id, reason);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase (id, y muchos otros parámetros);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, reason);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • etc (total de 20 casos de uso) ...

¿Cuáles son las soluciones?

1) Usar verbos :

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

¡Pero! Los verbos no se pueden usar en la url (en la teoría REST).

2) Use el estado completado (que será después del caso de uso):

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

Personalmente para mí se ve feo. ¿Puede ser que esté equivocado?

3) Utilice la solicitud 1 PUT para todas las operaciones:

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

Es difícil de manejar en el back-end. Además, es difícil de documentar. Dado que 1 acción tiene muchas variantes diferentes de solicitudes, de las cuales ya depende de respuestas específicas.

Todas las soluciones tienen inconvenientes significativos.

He leído muchos artículos sobre el REST en Internet. En todas partes solo una teoría, ¿cómo estar aquí con mi problema específico?


Diseña tu api de descanso independientemente de la capa de dominio.

Uno de los conceptos clave del diseño impulsado por dominio es el bajo acoplamiento entre las diferentes capas de software. Entonces, cuando diseñas tu API de descanso, piensas en la mejor API de descanso que puedas tener. Entonces, la función de la capa de aplicación es llamar a los objetos de dominio para realizar el caso de uso requerido.

No puedo diseñar su API de descanso para usted, porque no sé lo que está tratando de hacer, pero aquí hay algunas ideas.

Según lo entiendo, tienes un recurso Deal. Como dijiste, la creación / eliminación es fácil:

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

Entonces, quieres "mantener" un trato. No sé lo que eso significa, tienes que pensar en lo que cambia en el recurso "Deal". ¿Cambia un atributo? Si es así, entonces simplemente está modificando el recurso Deal.

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

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

¿Agrega algo? ¿Puedes tener varias retenciones en un trato? Me parece que "mantener" es un sustantivo. Si es feo, busca un sustantivo mejor.

POST / rest / {version} / deals / {id} / holds

{ reason: "something" }

otra solución: olvidar la teoría REST. Si crees que tu API sería más clara, más eficiente, más simple con el uso de verbos en la url, entonces hazlo por todos los medios. Probablemente puedas encontrar una manera de evitarlo, pero si no puedes, no hagas algo feo solo porque es la norma.

Mire la API de Twitter : muchos desarrolladores dicen que Twitter tiene una API bien diseñada. Tadaa, ¡usa verbos! A quién le importa, siempre y cuando sea genial y fácil de usar?

No puedo diseñar su API para usted, usted es el único que conoce sus casos de uso, pero volveré a decir mis dos consejos:

  • Diseñe la API de reposo por sí misma, y ​​luego use la capa de aplicación para llamar a los objetos de dominio apropiados en el orden correcto. Para eso está exactamente la capa de aplicación.
  • No sigas las normas y teorías a ciegas. Sí, debes intentar seguir las mejores prácticas y normas tanto como sea posible, pero si no puedes, entonces déjalos atrás (después de una cuidadosa consideración del curso)

Separé los casos de uso (UC) en 2 grupos: comandos y consultas (CQRS) y tengo 2 controladores REST (uno para los comandos y otro para las consultas). Los recursos REST no tienen que ser objetos modelo para realizar operaciones CRUD en ellos como resultado de POST / GET / PUT / DELETE. Los recursos pueden ser cualquier objeto que desee. De hecho, en DDD no debe exponer el modelo de dominio a los controladores.

(1) RestApiCommandController: un método por caso de uso de comando. El recurso REST en el URI es el nombre de la clase de comando. El método siempre es POST, porque usted crea el comando y luego lo ejecuta a través de un bus de comando (un mediador en mi caso). El cuerpo de la solicitud es un objeto JSON que mapea las propiedades del comando (los argumentos de la UC).

Por ejemplo: 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: un método por caso de uso de consulta. Aquí el recurso REST en el URI es el objeto DTO que devuelve la consulta (como el elemento de una colección, o solo uno solo). El método siempre es GET, y los parámetros de la consulta UC son parámetros en el URI.

Por ejemplo: 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); }

NOTA: El mediador pertenece a la capa de aplicación DDD. Es el límite de UC, busca el comando / consulta y ejecuta el servicio de aplicación apropiado.


Usar o no usar verbos en URL REST es un tema contradictorio. Sin embargo, si no desea usar verbos, siempre puede hacer PUT en /rest/{version}/deals y agregar el parámetro de consulta /rest/{version}/deals/{id}?action=hold . Siguiendo el mismo patrón, puede hacer una parte Action de su cuerpo de solicitud PUT.

Su capa de aplicación formará un comando más específico y lo enviará para que su lógica de dominio permanezca aislada en controladores de comando separados.