Статьи

Мартин Фаулер: встроенный документ

Пропускание структур данных JSON через сервер — это то, что я сейчас вижу больше. Документы JSON можно сохранить напрямую, используя  AggregateOrientedDatabase  или  сериализованный  большой объект в реляционной базе данных. Документы JSON могут также передаваться напрямую в веб-браузеры или использоваться для передачи данных на серверные средства визуализации страниц. Когда JSON используется таким образом, я слышу, как люди говорят, что использование объектно-ориентированного языка мешает, потому что JSON нужно переводить в объекты только для повторной визуализации — пустая трата усилий программирования  [1] . Я согласен с пунктом об отходах, но я утверждаю, что это не проблема с объектами, а неспособность понять инкапсуляцию.

Давайте представим, что мы храним заказ как документ JSON и обрабатываем его с помощью незначительной обработки на стороне сервера, снова как JSON. Пример документа может быть таким:

{ "id": 1234,
  "customer": "martin",
  "items": [
    {"product": "talisker", "quantity": 500},
    {"product": "macallan", "quantity": 800},
    {"product": "ledaig",   "quantity": 1100}
  ],
  "deliveries": [
    { "id": 7722,
      "shipDate": "2013-04-19",
      "items": [
        {"product": "talisker", "quantity": 300},
        {"product": "ledaig",   "quantity": 500}
      ]
    },
    { "id": 6533,
      "shipDate": "2013-04-18",
      "items": [
        {"product": "talisker", "quantity": 200},
        {"product": "ledaig",   "quantity": 300},
        {"product": "macallan", "quantity": 300}
      ]
    }
  ]
}

Мы предполагаем, что у нас нет большой обработки на стороне сервера, но у нас есть некоторые. Давайте также предположим, что мы используем язык ОО. Наивным подходом может быть чтение в документе JSON, преобразование данных в соответствующий граф объектов (с заказами, линейными позициями и поставками), применение любой обработки и затем сериализация объекта графа в JSON для клиента.

Во многих из этих ситуаций лучшим способом для продолжения является сохранение данных в JSONish-форме, но все же обертывание их объектами для координации манипуляций. Большинство сред программирования предоставляют универсальные библиотеки, которые принимают документ и десериализуют его в общие структуры данных. Таким образом, документ JSON будет десериализован в структуру списков и словарей, документ XML — в дерево узлов XML. Затем мы можем взять эту общую структуру данных и поместить ее в поле объекта заказа — вот пример с Ruby и JSON.

class Order...
  def initialize jsonDocument
    @data = JSON.parse(jsonDocument)
  end

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

class Order...
  def customer
    @data['customer']
  end
  def quantity_for aProduct
    item = @data['items'].detect{|i| aProduct == i['product']}
    return item ? item['quantity'] : 0
  end

Это включает случаи с более сложной логикой. [2]

class Order...
  def outstanding_delivery_for aProduct
    delivered_amount = @data['deliveries'].
      map{|d| d['items']}.
      flatten.
      select{|d| aProduct == d['product']}.
      inject(0){|res, d| res += d['quantity']}
    return quantity_for(aProduct) - delivered_amount
  end

Встроенный документ может быть дополнен перед отправкой клиенту.

class Order...
  def enrich
    @data['earliestShipDate'] = 
      @data['deliveries'].
      map{|d| Date.parse(d['shipDate'])}.
      min.
      to_s
  end

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

class Order...
  def deliveries
    @data['deliveries'].map{|d| Delivery.new(d)}
  end
class Delivery...
  def initialize hash
    @data = hash
  end
  def ship_date
    Date.parse(@data['shipDate'])
  end

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

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

Те, кто знаком с функциональным программированием, распознают стиль передачи общей структуры данных через ряд функций — вы можете думать об объекте как о предоставлении пространства имен для манипулирования общими структурами данных.

Приятным моментом для встроенного документа является то, что вы предоставляете документ в той же форме, в которой вы получаете его из хранилища данных, но все же хотите выполнить некоторые манипуляции с этими данными. Если вам не требуется доступ к содержимому документа JSON, нет необходимости даже десериализовать его в общую структуру данных. Для объекта заказа требуется только конструктор и метод для возврата его представления JSON. С другой стороны, по мере того, как вы будете больше работать с данными — больше логики на стороне сервера, трансформирующейся в разные представления — тогда стоит подумать, проще ли превратить данные в граф объектов.

1: Некоторые могут возразить, что это тоже пустая трата компьютерных усилий — хотя я был бы удивлен, если бы это было важно. Я бы, конечно, не принял аргумент производительности против преобразования в граф объектов, если бы он не сопровождался измерениями — как  любой аргумент производительности .

2: обратите внимание на цепочку  CollectionLambdas  в этом методе. Одно из моих домашних раздражений — слышать, как некоторые функциональные фанаты говорят, что этот стиль кода не является объектно-ориентированным. Хотя это может показаться чуждым для тех, кто имеет опыт работы с C ++ / Java, этот стиль совершенно естественен для небольших пользователей.