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

Шаблон проектирования для моделирования объектов запроса и ответа для веб-сервисов

У меня есть около 7 веб-сервисов REST для реализации. Некоторые из этих веб-служб имеют стандартный (идентичный) ответ, в то время как некоторые из них имеют разные ответы.

Запросы для этих веб-служб различны, но некоторые запросы и некоторые ответы имеют одни и те же базовые объекты данных.

Я не уверен, должен ли я создавать отдельные классы запроса/ответа для каждого веб-сервиса или повторно использовать стандартный. Я хотел бы знать, есть ли шаблон проектирования для моделирования объектов запроса и объектов ответа для этих веб-служб.

Хорошо, скажем, Account and Book - это два ресурса для отдыха, на которые будут работать мои веб-службы.

class Account {
    String username;
    String id;
}


class Book {
    String title;
    String isbn;
}

Итак, мои веб-службы выглядят так:

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

и т.д.

Теперь запрос CreateAccountandBook будет принимать объект учетной записи и список книг в полезной нагрузке. Также объект ответа для MYAPI/Account/getInfo/{id} имеет объект учетной записи и список книг, связанных с этой учетной записью. Но объект ответа также включает statusCode и Description.

Теперь я хотел бы создать классы для этих объектов запроса и ответа наилучшим образом.

Хорошо для начала.

У меня есть два абстрактных класса StandardRequest и StandardResponse.

Все классы запросов расширяют класс стандартного запроса и соответственно настраивают. Все классы ответов будут расширять стандартный класс ответа и соответственно настраивать.

Но эти запросы и ответ могут отличаться друг от друга, но все же повторно использовать одни и те же объекты объектов.

Например:

CreateAccountandBook объект запроса выглядит следующим образом:

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

а ответ для веб-службы getInfo:

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

поэтому существует перекрытие между классами запросов и ответов. Я могу создать два класса (req/res) для каждого веб-сервиса. Но хотелось бы знать, есть ли лучший способ моделирования этих классов.

4b9b3361

Ответ 1

У меня была аналогичная дилемма; Я пошел в общий, и мне нравятся результаты; не оглядывались назад.

Если бы у меня был метод API GetAccounts, подпись могла бы выглядеть.

public final Response<Account[]> getAccounts()

Естественно, тот же принцип может применяться к запросам.

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

По-моему; развязывание отдельных объектов от запросов и ответов дает более аккуратный домен и граф объектов.

Ниже приведен пример того, как может выглядеть такой общий объект ответа. В моем случае; Я построил сервер, чтобы получить общий ответ для всех запросов, чтобы улучшить обработку ошибок и уменьшить связь между объектами домена и объектами ответа.

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;
  }
}

Ответ 2

Я не знаю, есть ли такой шаблон дизайна. Я делаю следующее:

  • Для запросов GET определите параметры в строке запроса или в пути. Предпочтительным способом является путь. Кроме того, у вас будет несколько параметров для вашего сервиса. Каждый сервис будет обрабатывать это самостоятельно. Здесь нет возможности повторного использования.
  • Для запросов POST используйте параметры в формате JSON, которые входят в тело запроса. Кроме того, используйте адаптер (в зависимости от используемой технологии), который отобразит содержимое JSON в определенный класс, который вы получите как параметр.
  • Для ответов есть два подхода:

    • Вы можете создать собственный ResponseWrapper класс, который будет вашим реальным ответом. Это будет содержать код ответа, описание и поле, называемое значением, которое сохраняет реальный контент ответа в случае успешной обработки входных данных. Класс будет выглядеть следующим образом:

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

      В этом случае String value сохранит конкретный ответ в формате JSON. Например:

      @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
          }
      }
      
    • Повторно используйте Код ответа HTTP-ответа, используйте 200 (или 201, это зависит от типа запроса) для успешных запросов и правильный код состояния для ответа. Если ваш ответ имеет код состояния 200 (или 201), верните соответствующий объект в формате JSON. Если ваш ответ имеет другой код состояния, укажите объект JSON следующим образом:

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

Существует компромисс с использованием RESTful-сервисов с JSON или XML и ценой сложности для потребителей, которые могут не знать структуру ответа. В случае WS- * веб-сервисов компромисс приходит в условиях производительности (сравнивается тон RESTful).

Ответ 3

 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
    }
}

Ответ 4

О

если существует шаблон проектирования для моделирования объектов запроса и объектов ответа

стандартный способ, вы можете рассмотреть шаблон проектирования команды. Так как он позволяет инкапсулировать запрос команды в качестве объекта. И тем самым позволяя вам параметризовать клиентов с различными запросами, запросами очереди или журналов, ответами и отменой операций и т.д.

В качестве примера реализации:

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

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

Ответ 5

Большая проблема, которую я вижу во всех ответах до сих пор, включая вопрос, состоит в том, что все они нарушают принцип разделения проблем, скрытия информации и инкапсуляции. Во всех ответах запрос (и ответ) классы тесно связаны с классами моделей. Это более сложная проблема и поднимает вопрос, более важный, чем взаимосвязь между запросами и ответами...

Какая должна быть связь между классами запроса/ответа и классами модели?

Так как класс запроса (например, CreateBookRequest) и книга класса модели имеют в основном одни и те же свойства данных, вы можете выполнить любое из следующих действий:

а. Поместите все ваши данные/геттеры/сеттеры в класс Book и создайте CreateBookRequest из класса

В. Пусть ваш экземпляр CreateBookRequest содержит книгу в качестве члена (как в вопросе и ответах, заданных экостадиновым, Хуан Хенао, и общий вариант, предоставляемый dasm80x86, также является особым случаем)

С. Поместите данные/геттеры/сеттеры в BookBase и получите как Book, так и CreateBookRequest от BookBase

Д. Поместите все/некоторые данные/геттеры/сеттеры в BookStuff и сохраните Book и CreateBookRequest в BookStuff

Е. Поместите все данные/геттеры/сеттеры в Book и CreateBookRequest. (вы можете скопировать-вставить).

Правильный ответ: E. Мы все так подготовлены и стремимся "повторно использовать", что это наименее интуитивный ответ.

Класс запроса CreateBookRequest (а также класс ответа CreateBookResponse) и классная книга модели НЕ должны быть в той же иерархии классов (кроме обоих, имеющих объект как самый старший из родителей) (A, C). Также CreateBookRequest не должен ссылаться/содержать в модельной книге или на любой из составных классов, которые являются членами класса Book (B, D)

Причины этого следующие:

1) Вы хотите изменить объект модели или объект запроса независимо друг от друга. Если ваш запрос относится к вашему mdoel (как в A-D), любое изменение в модели будет отражено в интерфейсе и, следовательно, сломает ваш API. Ваши клиенты собираются писать клиентов в соответствии с API, продиктованными вашими классами запросов и ответов, и они не хотят изменять этих клиентов всякий раз, когда вы вносите изменения в классы моделей. вы хотите, чтобы запрос/ответ и модель менялись независимо.

2) Разделение проблем. Класс запроса CreateBookRequest может содержать все виды аннотаций и членов, связанных с интерфейсом/протоколом (например, аннотации проверки, которые JAX-RS знает, как обеспечить соблюдение). Эти аннотации, связанные с интерфейсом, не должны находиться в объекте модели. (как в A)

3) с точки зрения OO. CreateBookRequest не является книгой (а не IS_A) и не содержит книги.

Поток управления должен быть следующим:

1) Интерфейс/уровень управления (тот, который принимает вызовы Rest-API) должен использовать в качестве своих методов параметры классов запроса/ответа, определенных специально для этого уровня (например, CreateBookRequest). Пусть контейнер/инфраструктура создают те из запроса REST/HTTP/any.

2) Методы на интерфейсе/уровне управления должны каким-то образом создать экземпляр объекта класса модели и скопировать значения из классов запроса в объект класса модели,

3) Методы на уровне интерфейса/управления должны вызывать BO/Manager/Whatever (в слое модели..., который отвечает за бизнес-логику), передавая ему объект класса модели, а не класс интерфейса/объект класса параметра метода (другими словами, НЕ, как показал Луиджи Мендоса в своем ответе)

4) Метод model/BO вернет некоторый объект класса модели или некоторый "примитивный".

5) Теперь метод интерфейса (вызывающий) должен создать объект ответа класса интерфейса и скопировать значения в него из объекта класса модели, возвращаемого моделью /BO. (Как Луиджи Мендоса, как показано в его ответе)

6) Затем контейнер/инфраструктура создаст JSON/XML/любой ответ от объекта класса ответа.

Теперь вопрос задан... Какая должна быть связь между запросами и классами ответа?

Классы запросов должны распространяться на классы запросов, а не распространяться и не включать классы ответов, и наоборот. (как это было предложено и вопросом). Обычно у вас есть базовый класс BaseRequest, расширенный чем-то вроде CreateRequest, UpdateRequest и т.д.... где свойства, общие для всех запросов на создание, находятся в CreateRequest, который затем расширяется более конкретными классами запросов, такими как CreateBookRequest...
Аналогично, но параллельно с ним, это иерархия классов Response.

Вопросник также спросил, подходит ли как для CreateBookRequest, так и для CreateBookResponse, чтобы содержать один и тот же элемент, такой как (никогда не класс модели!) BookStuffInRequestAndResponse, какие свойства являются общими для запроса и ответа?

Это не столь серьезная проблема, поскольку запрос или ответ относятся к классу, на который также ссылается модель. Проблема заключается в том, что если вам нужно внести изменения в свой запрос API и сделать это в BookStuffInRequestAndResponse, это немедленно повлияет на ваш ответ (и наоборот).

Это не так плохо, потому что 1) если ваш клиент должен исправить свой клиентский код, потому что вы изменяете параметры запроса, они могут также исправить дескриптор/исправить измененный ответ и 2) наиболее вероятные изменения в запросе потребуют изменения ответ любым способом (например, добавление нового атрибута), однако это может быть не всегда так.

Ответ 6

вы можете попробовать Hexagonal Architecture, он состоит в том, чтобы разместить свою бизнес-логику посередине и получить доступ к ней через отдых, мыло, smtp и т.д.

и в ваших файлах Rest вам просто нужно открыть путь и вызвать класс со всеми вещами, которые вам нужны, например, DAO или что-то еще.

если служба ссылается на то же самое, вы можете оставить его в одном классе или отдельно в зависимости от предложения служб.

Для вашей проблемы вы можете создать DTO (объект передачи данных) с простыми данными, которые вам нужны, например номер счета, книги, описание, и вы можете переопределить toString(), чтобы дать правильный ответ или преобразовать данные в json reponse.

вы можете проверить http://alistair.cockburn.us/Hexagonal+architecture, я считаю, что это лучшая практика для этого