web-dev-qa-db-fra.com

Modèle de conception pour modéliser les objets de demande et de réponse pour les services Web

J'ai environ 7 REST services Web à implémenter. Certains de ces services Web ont une réponse standard (identique), tandis que d'autres ont des réponses différentes.

Les demandes de ces services Web sont différentes, mais certaines demandes et certaines réponses ont les mêmes objets de données sous-jacents.

Je ne sais pas si je dois créer des classes de demande/réponse distinctes pour chaque service Web ou en réutiliser un standard. Je voudrais savoir s'il existe un modèle de conception pour modéliser les objets de demande et les objets de réponse pour ces services Web.

Ok, disons que le compte et le livre sont deux ressources de repos sur lesquelles mes services Web travailleront.

class Account {
    String username;
    String id;
}


class Book {
    String title;
    String isbn;
}

Donc, mes services Web ressemblent à ceci:

MYAPI/CreateAccountandBook
MYAPI/Account/Create
MYAPI/Book/Create
MYAPI/Book/Update/{isbn}
MYAPI/Account/Update/{id}
MYAPI/Account/getInfo/{id} 

etc.

Maintenant, la requête CreateAccountandBook prendra un objet de compte et une liste de livres dans la charge utile. Également l'objet de réponse pour MYAPI/Account/getInfo/{id} a un objet de compte et une liste de livres associés à ce compte. Mais l'objet de réponse comprend également un statusCode et Description.

Maintenant, je voudrais créer des classes pour ces objets de demande et de réponse de la meilleure façon possible.

D'accord pour commencer.

J'ai deux classes abstraites StandardRequest et StandardResponse.

Toutes les classes de demandes étendront la classe de demande standard et seront personnalisées en conséquence. Toutes les classes de réponse étendront la classe de réponse standard et seront personnalisées en conséquence.

Mais ces demandes et réponses peuvent être très différentes les unes des autres, mais toujours réutiliser les mêmes objets d'entité.

Par exemple:

createAccountandBook l'objet de requête ressemble à ceci:

class CreateAccountAndBookRequest {
   Account account;
   List<Book> books;
}

tandis que la réponse pour le service Web getInfo est:

class GetInfoResponse {
   Account account;
   List<Book> books;
   String statusCode;
   String description;
}

il y a donc chevauchement entre les classes de demande et de réponse. Je peux créer deux classes (req/res) pour chaque service Web. Mais j'aimerais savoir s'il existe une meilleure façon de modéliser ces classes.

26
DntFrgtDSemiCln

J'avais un dilemme similaire; Je suis allé dans la direction générique et j'aime les résultats; n'ont pas regardé en arrière depuis.

Si j'avais une méthode API GetAccounts, la signature pourrait ressembler.

public final Response<Account[]> getAccounts()

Naturellement, le même principe peut être appliqué aux demandes.

public final Response<Account[]> rebalanceAccounts(Request<Account[]>) { ... }

À mon avis; le découplage des entités individuelles des demandes et des réponses produit un domaine et un graphique d'objet plus nets.

Voici un exemple de ce à quoi pourrait ressembler un tel objet de réponse générique. Dans mon cas; J'avais construit le serveur pour avoir une réponse générique pour toutes les demandes afin d'améliorer la gestion des erreurs et de réduire le couplage entre les objets de domaine et les objets de réponse.

public class Response<T> {

  private static final String R_MSG_EMPTY = "";
  private static final String R_CODE_OK = "OK";

  private final String responseCode;
  private final Date execDt;
  private final String message;

  private T response;

  /**
   * A Creates a new instance of Response
   *
   * @param code
   * @param message
   * @param execDt
   */
  public Response(final String code, final String message, final Date execDt) {

    this.execDt = execDt == null ? Calendar.getInstance().getTime() : execDt;
    this.message = message == null ? Response.R_MSG_EMPTY : message;
    this.responseCode = code == null ? Response.R_CODE_OK : code;
    this.response = null;
  }

  /**
   * @return the execDt
   */
  public Date getExecDt() {

    return this.execDt;
  }

  /**
   * @return the message
   */
  public String getMessage() {

    return this.message;
  }

  /**
   * @return the response
   */
  public T getResponse() {

    return this.response;
  }

  /**
   * @return the responseCode
   */
  public String getResponseCode() {

    return this.responseCode;
  }

  /**
   * sets the response object
   *
   * @param obj
   * @return
   */
  public Response<T> setResponse(final T obj) {

    this.response = obj;
    return this;
  }
}
11
dasm80x86

Le gros problème que je vois dans toutes les réponses jusqu'à présent, y compris la question, est qu'elles violent toutes le principe de la séparation des préoccupations, de la dissimulation des informations et de l'encapsulation. Dans toutes les réponses, les classes de demande (et de réponse) sont étroitement liées aux classes de modèle. C'est un problème plus grave qui soulève une question plus importante que la relation entre les demandes et les réponses ...

Quelle devrait être la relation entre les classes de demande/réponse et les classes de modèle?

Étant donné que la classe de demande (par exemple CreateBookRequest) et la classe de modèle Book ont ​​principalement les mêmes propriétés de données, vous pouvez effectuer l'une des opérations suivantes:

A. Mettez toutes vos données/getters/setters dans la classe Book et faites étendre CreateBookRequest à partir de la classe

B. Demandez à votre CreateBookRequest de contenir un livre en tant que membre (comme dans la question et les réponses données par ekostadinov, Juan Henao,. L'utilisation générique donnée par dasm80x86 en est également un cas particulier)

C. Placez les données/getters/setters dans BookBase et faites étendre à la fois Book et CreateBookRequest à partir de BookBase

D. Mettez toutes/certaines données/getters/setters dans BookStuff et faites que Book et CreateBookRequest contiennent un BookStuff

E. Mettez toutes les données/getters/setters dans Book et CreateBookRequest. (vous pouvez copier-coller).

La bonne réponse est E. Nous sommes tous si bien entraînés et désireux de "réutiliser" que c'est la réponse la moins intuitive.

La classe de demande CreateBookRequest (ainsi que la classe de réponse CreateBookResponse) et la classe de modèle Book, ne doivent PAS être dans la même hiérarchie de classe (à l'exception des deux ayant Object comme parent le plus élevé) (A, C). De plus, CreateBookRequest ne doit pas faire référence/contenir au modèle Book ou à l'une des classes composites membres de la classe Book (B, D)

Les raisons en sont les suivantes:

1) Vous souhaitez modifier l'objet de modèle ou l'objet de demande indépendamment les uns des autres. Si votre demande se réfère à votre mdoel (comme dans A-D) tout changement dans le modèle sera reflété dans l'interface, et donc cassera votre API. Vos clients vont écrire des clients selon l'API dictée par vos classes de demande/réponse et ils ne veulent pas changer ces clients chaque fois que vous modifiez vos classes de modèle. vous souhaitez que la demande/réponse et le modèle varient indépendamment.

2) Séparation des préoccupations. Votre classe de demande CreateBookRequest peut contenir toutes sortes d'annotations et de membres liés à l'interface/au protocole (par exemple, des annotations de validation que l'implémentation JAX-RS sait comment appliquer). Ces annotations liées à l'interface ne doivent pas se trouver dans l'objet modèle. (comme en A)

3) à partir d'une perspective OO CreateBookRequest n'est pas un livre (pas IS_A) ni ne contient un livre.

Le flux de contrôle doit être le suivant:

1) La couche interface/contrôle (celle qui reçoit les appels Rest-API) doit utiliser comme méthodes ses paramètres Paramètres classes Request/Response définies spécifiquement pour cette couche (par exemple CreateBookRequest). Laissez le conteneur/l'infrastructure créer ceux à partir de la demande REST/HTTP/quelle que soit.

2) Les méthodes de la couche interface/contrôle doivent créer en quelque sorte une instance d'un objet de classe de modèle et copier les valeurs des classes de demande dans l'objet de classe de modèle,

3) Les méthodes de la couche interface/contrôle doivent appeler un BO/Manager/Whats (dans la couche modèle ... qui est responsable de la logique métier) en lui passant l'objet classe modèle et non la classe interface/classe paramètre paramètre objet (en d'autres termes, PAS comme Luiggi Mendoza l'a montré dans sa réponse)

4) La méthode model/BO retournerait un objet de classe de modèle ou une "primitive".

5) Maintenant, la méthode d'interface (l'appelant) doit créer un objet de réponse de classe d'interface et y copier des valeurs à partir de l'objet de classe de modèle renvoyé par le modèle/BO. (Tout comme Luiggi Mendoza comme indiqué dans sa réponse)

6) Le conteneur/l'infrastructure créerait alors la réponse JSON/XML/quelle que soit l'objet de classe de réponse.

Passons maintenant à la question posée ... Quelle devrait être la relation entre les demandes et les classes de réponse?

Les classes de demande doivent s'étendre à partir des classes de demande et ne pas étendre ni contenir de classes de réponse, et vice versa. (comme l'a également suggéré le demandeur). Habituellement, vous avez une classe BaseRequest très basique, étendue par quelque chose comme CreateRequest, UpdateRequest, etc ... où les propriétés communes à toutes les demandes de création sont dans CreateRequest qui est ensuite étendu par des classes de demande plus spécifiques telles que CreateBookRequest ...
De même, mais parallèlement à elle, est la hiérarchie des classes de réponse.

Le demandeur a également demandé s'il était acceptable que CreateBookRequest et CreateBookResponse contiennent le même membre, comme (jamais une classe de modèle!) BookStuffInRequestAndResponse, quelles propriétés communes à la demande et à la réponse?

Ce problème n'est pas aussi grave que le fait que la demande ou la réponse fasse référence à une classe à laquelle le modèle fait également référence. Le problème est que si vous devez apporter une modification à votre demande d'API et la faire dans BookStuffInRequestAndResponse, cela affecte immédiatement votre réponse (et vice versa).

Ce n'est pas si mal parce que 1) si votre client a besoin de corriger son code client parce que vous modifiez les paramètres de la demande, il peut aussi bien gérer/corriger la réponse modifiée et 2) les changements les plus probables dans la demande nécessiteraient une modification de la réponse (par exemple en ajoutant un nouvel attribut), cependant, cela peut ne pas toujours être le cas.

23
inor

Je ne sais pas s'il existe un tel modèle de conception. Je fais ce qui suit:

  • Pour les requêtes GET, définissez les paramètres dans la chaîne de requête ou dans le chemin. La voie préférée est le chemin. De plus, vous aurez peu de paramètres pour votre service. Chaque service s'en occupera de lui-même. Il n'y a pas de réutilisabilité ici.
  • Pour les requêtes POST, consommez les paramètres au format JSON qui figurent dans le corps de la requête. Utilisez également un adaptateur (selon la technologie que vous utilisez) qui mappera le contenu JSON sur une classe spécifique que vous recevez en paramètre.
  • Pour les réponses, il existe deux approches:

    • Vous pouvez créer une classe ResponseWrapper personnalisée qui sera votre vraie réponse. Celui-ci contiendra le code de réponse, la description et un champ appelé valeur qui stocke le contenu réel de la réponse en cas de réussite du traitement des données d'entrée. La classe ressemblera à ceci:

      public class ResponseWrapper {
          private int statusCode;
          private String description;
          private String value;
      }
      

      Dans ce cas, String value stockera la réponse concrète au format JSON. Par exemple:

      @Path("/yourapi/book")
      public class BookRestfulService {
      
          @POST("/create")
          @Produces("json")
          public ResponseWrapper createBook(Book book) {
              ResponseWrapper rw = new ResponseWrapper();
              //do the processing...
              BookService bookService = new BookService();
              SomeClassToStoreResult result = bookService.create(book);
              //define the response...
              rw.setStatusCode(...);
              rw.setDescription("...");
              rw.setValue( convertToJson(result) );
          }
      
          static String convertToJson(Object object) {
              //method that probably will use a library like Jackson or Gson
              //to convert the object into a proper JSON strong
          }
      }
      
    • Réutilisez le HTTP Response Status Code , utilisez 200 (ou 201, cela dépend du type de demande) pour les demandes réussies et un code d'état approprié pour la réponse. Si votre réponse a le code d'état 200 (ou 201), renvoyez l'objet approprié au format JSON. Si votre réponse a un code d'état différent, fournissez un objet JSON comme celui-ci:

      { "error" : "There is no foo in bar." }
      

Il y a un compromis à utiliser les services RESTful avec JSON ou XML, et c'est le prix de la complexité pour les consommateurs, qui peuvent ne pas connaître la structure de la réponse. Dans le cas des services Web WS- *, le compromis se fait en termes de performances (par rapport à l'approche RESTful par tonne).

3
Luiggi Mendoza
 public ResponseDto(){
     String username;
     int id;
     ArrayList books <Book> = new ArrayList<Book>();

    // proper getters and setters...
    @Override
    public String toString(){
       //parse this object to return a proper response to the rest service,
       //you can parse using some JSON library like GSON
    }
}
1
Juan Henao

À propos de

s'il existe un modèle de conception pour modéliser les objets de demande et les objets de réponse

la manière standard, vous pouvez considérer est modèle de conception de commande . Puisqu'il vous permet d'encapsuler une demande de commande en tant qu'objet. Et vous permettant ainsi de paramétrer les clients avec différentes demandes, demandes de file d'attente ou de journal, réponses et prise en charge des opérations annulables, etc.

Comme exemple d'implémentation:

  abstract class Request{
    public abstract void Execute();
    public abstract void UnExecute();
  } 

   class AccountAndBookRequest extends Request{
   Account account;
   List<Book> books;
   }
1
ekostadinov