API Rest et DDD

dans mon projet utilisant la méthodologie DDD.

le projet a l'accord global(entité). Cet agrégat comporte de nombreux cas d'utilisation.

pour cet agrégat, je dois créer une api rest.

avec standard: créer et supprimer aucun problème.

1) CreateDealUseCase(le nom, le prix et beaucoup d'autres params);

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

2) DeleteDealUseCase (id)

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

Mais que faire avec le reste de les cas d'utilisation?

  • HoldDealUseCase(id, de la raison);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase(id, et beaucoup d'autres params);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, reason);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • etc(total de 20 cas d'utilisation)...

quelles sont les des solutions?

1) utilisez les verbes:

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

Mais! Les verbes ne peuvent pas être utilisés dans l'url(en théorie du repos).

2) utilisez l'état complété(qui sera après la le cas d'utilisation):

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

personnellement pour moi, ça a l'air moche. Peut-être que je me trompe?

3)Utiliser 1 METTRE de la demande pour toutes les opérations:

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

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

il est difficile à manipuler dans le dos. En outre, il est difficile de document. Depuis 1 action a de nombreuses variantes différentes des requêtes, à partir de laquelle dépend déjà des réponses spécifiques.

toutes les solutions ont des inconvénients importants.

j'ai lu beaucoup d'articles sur le reste sur internet. Partout seulement une théorie, comment être ici avec mon problème spécifique?

27
demandé sur stalxed 2016-02-29 16:05:01

3 réponses

j'ai lu beaucoup d'articles sur le reste sur internet.

basé sur ce que je vois ici, vous avez vraiment besoin de regarder au moins une des conférences de Jim Webber sur le repos et le DDD

mais que faire des autres cas d'usage?

ignorez l'API pendant un moment - comment feriez-vous avec des formulaires HTML?

vous auriez probablement une page Web qui présente une représentation de Deal, avec un tas de liens dessus. Un lien vous conduirait à la forme HoldDeal, et un autre lien vous conduirait à la forme ChangePrice, et ainsi de suite. Chacun de ces formulaires aurait zéro ou plus de champs à remplir, et les formulaires seraient chacun postés à une certaine ressource pour mettre à jour le modèle de domaine.

est-ce qu'ils posteraient tous vers la même ressource? Peut-être, peut-être pas. Ils ont tous les même type de média, donc s'ils étaient tous postés sur le même terminal web, vous auriez à décoder le contenu de l'autre côté.

compte tenu de cette approche, comment implémentez-vous votre système? Le type média veut être json, basé sur vos exemples, mais il n'y a rien de mal avec le reste.

1) Utilisez les verbes:

C'est très bien.

Mais! Les verbes ne peuvent pas être utilisés dans l'url(dans le reste théorie.)

messagerie Unifiée... aucun. Le reste ne se soucie pas de l'orthographe de vos identificateurs de ressources. Il y a un tas de bonnes pratiques URI qui prétendent que les verbes sont mauvais - c'est vrai - mais ce n'est pas quelque chose qui découle du repos.

mais si les gens sont si agités, vous nommez le point final de la commande à la place du verbe. (par exemple:" hold " n'est pas un verbe, c'est un cas d'utilisation).

Utiliser 1 METTRE de la demande pour tous les opérations:

honnêtement, celui-là n'est pas mal non plus. Vous ne voudrez pas partager l'uri (en raison de la façon dont la méthode PUT est spécifiée), mais utilisez un modèle où les clients peuvent spécifier un identifiant unique.

Voici la viande: vous construisez une api sur les verbes HTTP et HTTP. HTTP est conçu pour transfert de documents. Le client vous donne un document, décrivant une modification demandée dans votre modèle de domaine, et vous appliquez la modification à le domaine (ou non), et retourner un autre document décrivant le nouvel état.

empruntant le vocabulaire du CQRS pour un moment, vous postez des commandes pour mettre à jour votre modèle de domaine.

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

Justification - vous mettez une commande spécifique (une commande avec un Id spécifique) dans la file d'attente de commande, qui est une collection.

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

Ouais, c'est bien trop.

jetez un autre coup d'oeil aux RESTBucks. C'est un protocole de café, mais toute l'api c'est juste passer de petits documents pour faire avancer la machine d'état.

21
répondu VoiceOfUnreason 2017-10-11 11:26:02

Concevez votre api rest indépendamment de la couche domaine.

L'un des concepts clés de la conception par domaine est couplage faible entre vos différentes couches logicielles. Ainsi, lorsque vous concevez votre api rest, vous pensez à la meilleure api rest que vous pourriez avoir. Puis, c'est le rôle de la couche application pour appeler les objets du domaine pour effectuer les cas d'utilisation.

Je ne peux pas concevoir votre api rest pour vous, parce que je ne sais pas ce que vous essayez de faire, mais voici quelques idées.

D'après ce que j'ai compris, vous avez une ressource de transaction. Comme vous l'avez dit, la création/suppression sont faciles :

  • POST /le reste/{version}/offres spéciales d'
  • SUPPRIMER /le reste/{version}/offres/{id}.

alors, vous voulez "tenir" un marché. Je ne sais pas ce que cela signifie, Vous devez penser à ce qu'il change dans la ressource "Deal". Faut-il changer un attribut? si oui, alors vous modifiez simplement la ressource Deal.

METTRE /le reste/{version}/offres/{id}

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

est-ce que ça ajoute quelque chose? Pouvez-vous avoir plusieurs suspensions sur une affaire? Me semble que "hold" est un substantif. Si c'est moche, trouver un meilleur nom.

POST /le reste/{version}/offres/{id}/contient

{
    reason: "something"
}

une autre solution: oublier la théorie du repos. Si vous pensez que votre api serait plus clair, plus efficace, plus simple avec l'utilisation de verbes dans l'url, puis par tous les moyens, de le faire. Vous pouvez probablement trouver un moyen de l'éviter, mais si vous ne pouvez pas, ne pas le faire quelque chose de moche juste parce que c'est la norme.

Regardez api twitter: beaucoup de développeurs disent que twitter a une API bien conçue. Tadaa, il utilise des verbes! On s'en fout, tant que c'est cool et facile à utiliser?

Je ne peux pas concevoir votre api pour vous, vous êtes le seul à connaître vos cas d'utilisation, mais je vais répéter mes deux conseils:

  • concevoir l'api rest par elle-même, puis utiliser la couche application pour appeler les objets de domaine appropriés le bon ordre. C'est exactement pour cela que la couche application est ici.
  • ne suivez pas aveuglément les normes et les théories. Oui, vous devriez essayer de suivre les bonnes pratiques et les normes autant que possible, mais si vous ne pouvez pas les laisser derrière (après un examen attentif bien sûr)
11
répondu Kaidjin 2016-02-29 13:31:54

je sépare les cas d'utilisation (UCs) en 2 groupes: commandes et requêtes (CQRS), et j'ai 2 contrôleurs REST (un pour les commandes et un autre pour les requêtes). Les ressources REST n'ont pas besoin d'être des objets modèles pour effectuer des opérations CRUDS sur eux à la suite de POST/GET/PUT/DELETE. Les ressources peuvent être tout objet que vous voulez. En effet dans DDD vous ne devriez pas exposer le modèle de domaine aux contrôleurs.

(1) RestApiCommandController: une méthode par cas d'utilisation de commande. reste la ressource dans L'URI est le nom de la classe de commande. La méthode est toujours POST, parce que vous créez la commande, et ensuite vous l'exécutez via un bus de commande (un médiateur dans mon cas). Le corps de la requête est un objet JSON qui permet de cartographier les propriétés de la commande (les args de L'UC).

Par exemple: 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: une méthode par cas d'utilisation de requête. Ici, la ressource REST dans L'URI est l'objet DTO que la requête retourne (comme l'élément d'une collection, ou juste un seul). La méthode est toujours GET, et les paramètres de la requête UC sont params dans L'URI.

Par exemple: 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);
} 

NOTE: Mediator appartient à la couche application DDD. C'est la limite UC, il recherche la commande/requête, et exécute le service d'application approprié.

0
répondu choquero70 2017-06-29 23:18:48