Как команда Hema из Alibaba использует дизайн на основе доменов (DDD) для создания эффективной, гибкой и готовой к эволюции архитектуры кодирования, отвечающей интенсивным бизнес-потребностям нового типа супермаркетов.

Позвольте мне представиться. Меня зовут Цуньхуи, я старший инженер по персоналу в Hema (盒 马, офлайн-супермаркете Alibaba, подключенном к цифровым технологиям). Моя карьера программиста длилась много лет, и за это время я увидел и написал много строк кода. Создание структур кодирования, доставляющих программные продукты высочайшего качества, - постоянная тема разговоров между мной и другими программистами. В последнее время в ходе этих разговоров я всегда рано или поздно поднимаю тему предметно-ориентированного проектирования (DDD).

У Ace-программистов разные точки зрения на DDD. DDD - один из многих подходов к программированию, который далек от совершенства или обладает огромными преимуществами по сравнению с другими подходами. Что касается моих личных взглядов, то я охотнее прокомментирую, привлек ли сам дизайн должное внимание. На мой взгляд, хороший дизайн - это хороший дизайн, независимо от используемого подхода.

Большая часть кода, с которым я сталкиваюсь, не основана на DDD. Даже методы с менее строгими стандартами, которые измеряют окончательный дизайн на основе того, совместим ли генерирующий его код с различными подходами, не увеличивают значительно общий пул доступного кода. Большинство из них выглядят как «спагетти-код», который завершает операцию прямым обращением к базам данных с терминалов. Большинство проектов сосредоточено на базах данных, иногда даже без использования дизайна базы данных, оставляя кучу полей, которые заставляют задуматься, зачем они там.

Кодирование не должно сводиться к простому написанию кучи кода; код должен быть простым и элегантным. Мощный потенциал тестирования гарантирует, что наши программные продукты будут отличными внешне (снимаем шляпу перед специалистами по стресс-тестированию Hema!). Однако внутреннее качество указанных продуктов постоянно игнорируется. Все ресурсы вкладываются в завершение проектов в сжатые сроки, в результате чего внутреннее качество все больше отстает от ожидаемого.

Услуги Hema как полноценного супермаркета больше ориентированы на бизнес. Вся цепочка от поставки до распределения сложна и сильно взаимосвязана. Невозможно держать все в порядке, не разобравшись в различных отношениях. Дизайн чрезвычайно важен в этой области. Неправильный дизайн может стать огромной проблемой, с которой могут столкнуться лица, ответственные за последующие шаги.

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

Модели предметной области: объектно-ориентированная и база данных

Это два широко используемых подхода к проектированию в DDD:

1. Дизайн базы данных: данные абстрагируются, а их отношения определяются как базы данных (также известные как моделирование данных).

2. Объектно-ориентированный дизайн: данные абстрагируются, а их отношения определяются как объекты (также известное как объектное моделирование).

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

Дизайн базы данных

Модель предметной области (модель данных), ранее известная как «словарь данных», для этого режима проектирования обычно используется опытными архитекторами кодирования. Ясность модели предметной области определяет внутреннее качество программных продуктов. Продукты, построенные с использованием правильно структурированной доменной модели, имеют четкую структуру, которая позволяет легко вносить изменения и обеспечивать доступную реализацию будущих изменений. Архитектор играет важную роль в команде разработчиков, определяя структуру программного обеспечения, которая в конечном итоге определяет будущую читаемость, масштабируемость и возможности развития программного обеспечения. Как правило, архитектор проектирует модель предметной области, а разработчики используют модель предметной области в качестве структуры для написания своего кода. Модель предметной области - это, по сути, основа проектирования баз данных.

Архитекторы постоянно развивают модель предметной области, основываясь на обсуждениях спроса. Некоторые дизайнеры пишут модель предметной области в виде операторов SQL, которые развиваются в процессе, сравнимом с процессом розового куста:

1. Создается единый стол (засевается семя)

2. Создается несколько таблиц (семена укореняются и прорастают)

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

4. Исправлен дизайн (садовник подрезает лишние побеги).

5. Финальный запуск (цветение розы).

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

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

· На уровне обслуживания предпочтительный менеджер настраивается для управления большей частью соответствующей логики.

· POJO, или модель предметной области кровопотери (объясненная в следующем разделе), служит данными и постоянно модифицируется и комбинируется менеджером (совестью отца).

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

Используя таблицы «Отец» и «Сын», сгенерированный POJO:

public class Father{…}
public class Son{
	private String fatherId;//The fatherId in the table serves as the external key of the Father Table id
	public String getFatherId(){
		return fatherId;
	}
	……
}

Теперь предположим, что наш гипотетический «Сын» делает что-то непослушное, и его «Отец» должен его отругать, оставляя и «Сына», и «Отца» в эмоциональной боли. Управляющий действует как совесть «Отца» и направляет его ругать «Сына», используя следующий процесс:

public class SomeManager{
	public void fatherSlapSon(Father father, Son son){
		//Please be understanding if the logic does not make sense
		father.setPainOnHand();
		son.setPainOnFace();//Assume that emotionalpain is a database field
	}
}

Объектно-ориентированный дизайн

При работе с DDD полезно исходить из предположения, что, если ваша машина постоянно поддерживает бесконечную память, постоянство данных и, следовательно, базы данных, не требуется. Столкнувшись с этим сценарием, эксперты Alibaba Hema рекомендуют разрабатывать программное обеспечение в соответствии с философией, которую они называют
«постоянным невежеством».

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

По мнению экспертов Alibaba Hema, объектно-ориентированное моделирование является идеальным подходом к моделированию по сравнению с процессно-ориентированным моделированием. Функции класса и таблицы в чем-то схожи, и некоторые эксперты считают, что вместе со строкой и объектом они имеют соответствующие отношения.

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

Ниже приведены объяснения важных аспектов выражений предметной области:

· Ссылка:

Реляционные базы данных представляют отношение «многие ко многим» с помощью третьей таблицы. Эта модель предметной области относится к невизуализации. Персонал, отвечающий за бизнес-системы, не понимает этого режима.

· Уплотнение:

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

· Последовательность / полиморфизм:

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

Вернемся к примеру, когда рассерженный отец ругал своего сына:

public class Father{
	//The father scolds his son on his own, regardless of whether his conscience (the manager) offers help
	public void slapSon(Son son){
		this.setPainOnHand();
		son.setPainOnFace();
	}
}

Следуя этому подходу, мы постепенно проектируем живые модели предметной области в объектно-ориентированной среде. Уровень обслуживания представляет собой набор точных операций, основанных на этих моделях (он становится тоньше и оставляет многие действия для обработки объектами предметной области). Модели предметной области не завершают операции. Каждый объект домена выполняет свое предназначение (единственная ответственность).

Рассмотрим еще один гипотетический пример бегущего человека. «Person.run» - это действие, не связанное с операциями, но менеджер или служба могут, вызвав person.run, завершить забег на 100 метров или доставить заказ еды на вынос, среди других операций. Результирующая архитектура будет похожа на следующую фигуру:

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

· [Хранение] Хранит данные об объекте на долговечном носителе.

· [Получение] Эффективно возвращает результаты запроса данных в память.

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

А теперь давайте снова посмотрим на архитектуру:

Следует выделить следующие концепции:

· Модели предметной области используются в операциях с предметной областью. Их можно использовать для чтения, но не без платы. При этом предварительном условии один агрегат может содержать некоторые данные, которые поддерживают действия, аналогичные getById, но не применимы к запросу. Обслуживание запросов не является первоначальной целью DDD.

· Запросы основаны на базах данных. Сложные запросы должны избегать уровня домена и напрямую взаимодействовать с базами данных.

Дополнительная обрезка: операции с доменом ›объекты› запрос данных ›строки таблицы.

Кровопотеря, анемия и обширные доменные модели

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

Модель домена кровопотери

DDD, ориентированный на базу данных, является типичным примером модели кровопотери. Для Java POJO предлагает только простые методы установки и получения на основе полей. Отношения между POJO скрыты в определенных идентификаторах объектов и объясняются внешним менеджером. Используя наш предыдущий пример son.fatherId, когда «Сын» не знает о своих отношениях с «Отцом», менеджер обращается к «Отцу» через son.fatherId.

Модель анемической области

Чтобы объяснить модель анемичной области, давайте вернемся к нашему примеру отца и сына. Если сын не знает, кто его отец, находит ли он его с помощью проверки ДНК (son.FatherId), используя каждый раз посредника (менеджера)? Нет, это невозможно. Модель предметной области можно сделать богаче, настроив класс «Сын» следующим образом:

public class Son{
	private Father father;
	public Father getFather(){return this.father;}
}

Класс «Сыновья» стал более разнообразным. Однако возникает еще одно неудобство, заключающееся в том, что невозможно узнать «Сына» через «Отца». Почему отец не знает, кто его сын? Чтобы решить эту проблему, мы добавляем в «Отец» следующие атрибуты:

public class Father{
	private Son son;
	private Son getSon(){return this.son;}
}

Теперь эти два класса стали более устойчивыми и превратились в то, что мы называем моделью анемичной области. Эта модель создала достойную «семью», в которой «Отец» и «Сын» знают друг друга. Но при более внимательном рассмотрении этих двух классов обнаруживается кое-что проблемное: объект обычно получается с использованием репозитория (запрос к базе данных) или фабрики (новая память), как показано на следующем рисунке:

Son someSon = sonRepo.getById(12345);

Этот метод извлекает объект «Сын» из базы данных. Чтобы построить законченный объект «Сын», sonRepo требуется объект FatherRepo для создания объекта «Отец», который будет назначен для son.father. Точно так же, чтобы построить полноценного «Отца», для FatherRepo требуется sonRepo, чтобы создать сына, который будет назначен на Father.son. В результате образуется неориентированная петля. Эту проблему рекурсивного вызова можно решить. Однако это происходит за счет того, что модель предметной области становится импровизированной, что недопустимо для разработчиков, преследующих чисто предметные модели.

Для команды Hema из Alibaba наша цель прямо противоположная: целеустремленность и отсутствие петель. Вопрос в том, чтобы предотвратить этот рекурсивный вызов, можем ли мы опустить ссылку в классах «Отец» и «Сын»? Отцовский класс модифицируется следующим образом:

public class Father{
	//private Son son; Delete this reference
	private SonRepository sonRepo;//Add a repo to the Son
	private getSon(){return sonRepo.getByFatherId(this.id);}
}

Таким образом, построение «Отца» не создает еще одного нежелательного «Сына». Однако цена, которую мы здесь платим, заключается в том, что SonRepository вводится в класс «Отец». Другими словами, в доменном объекте есть ссылка на операцию сохранения. Мы называем это моделью богатой предметной области, которая представлена ​​в следующем разделе.

Модель богатой предметной области

Существование богатых доменных моделей приводит к тому, что доменные модели теряют свою чистоту. Эти модели больше не являются чистым объектом памяти, а скрывают операцию с базой данных, что не сулит ничего хорошего для тестирования. Перед подключением к базе данных выполняется быстрый модульный тест (подробно описано ниже). Иногда для обеспечения полноты модели требуются расширенные модели предметной области.

Давайте возьмем другой гипотетический сценарий: в магазине Hema есть тысячи товаров на полке, каждая из которых имеет сотни атрибутов. Если все эти предметы необходимо продемонстрировать при строительстве магазина, эффективность будет низкой:

public class Shop{
	//private List<Product> products; This list of items being built is too large
	private ProductRepository productRepo;
	public List<Product> getProducts(){
		//return this.products;
		return productRepo.getShopProducts(this.id);
	}
}

На этом этапе также целесообразно дать краткое описание внедрения зависимостей:

· Внедрение зависимостей - это одноэлементный объект во время выполнения. Только объекты (@Component) в области сканирования Spring могут использовать внедрение зависимостей через аннотацию (@Autowired). Новые объекты не могут получить инъекцию зависимости через аннотацию.

· Эксперт Alibaba Hema, Хуэй Цзы, рекомендует внедрение зависимостей конструктора, которое удобно для тестирования, строит идеально завершенные объекты и явно сообщает программисту, какие объекты они должны имитировать / заглушить.

Теперь давайте вернемся к модели богатой предметной области и вернемся к нашему примеру отца и сына:

public class Father{
	private SonRepository sonRepo;
	private Son getSon(){return sonRepo.getByFatherId(this.id);}
	public Father(SonRepository sonRepo){this.sonRepo = sonRepo;}
}

В этом сценарии создание нового «Отца» требует назначения SonRepository, что затрудняет написание кода. Возникает вопрос: является ли внедрение SonRepository путем внедрения зависимостей привлекательным вариантом? Здесь «Отец» не является одноэлементным объектом, который можно воссоздать в сценариях New и Query. Внедрение SonRepository невозможно при построении «Отца». Подобные ситуации возникают, когда заводской режим, который некоторые считают бесполезным, демонстрирует свою ценность:

@Component
public class FatherFactory{
	private SonRepository sonRepo;
	@Autowired
	public FatherFactory(SonRepository sonRepo){}
	public Father createFather(){
		return new Father(sonRepo);
	}
}

Поскольку FatherFactory - это сгенерированный системой одноэлементный объект, SonRepository можно естественным образом внедрить в Factory. Метод newFather скрывает sonRepo этой инъекции, и, таким образом, новый Отец становится чистым.

Удобные для тестирования модели предметной области

Модели кровопотери и анемии являются объектами чистой памяти и изначально удобны для тестирования. Но на практике модели богатой предметной области действительно существуют. Чтобы удалить полнофункциональные модели предметной области, объект предметной области должен быть разобран и, таким образом, становится несколько сбитым с толку. Откровенно говоря, битва между анемичными и богатыми моделями предметной области никогда не прекращается. В моделях расширенной предметной области объекты обладают свойством постоянства и, таким образом, становятся зависимыми от баз данных. Основное требование состоит в том, чтобы имитировать / заглушить эти зависимости. Давайте еще раз посмотрим на наш пример с «Отцом»:

public class Father{
	private SonRepository sonRepo;//=new SonRepository() Construction is not possible here
	private getSon(){return sonRepo.getByFatherId(this.id);}
	// Place in the constructor function
	public Father(SonRepository sonRepo){this.sonRepo = sonRepo;}
}

Размещение SonRepository в функции конструктора подтверждает, подходит ли он для тестирования. Модульное тестирование может быть выполнено плавно путем имитации / заглушки репозитория.

Как Hema реализует репозиторий

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

В Hema мы разработали Tunnel, уникальный интерфейс, который позволяет получать доступ к объектам домена в различных типах баз данных. Репозиторий не выполняет непосредственное сохранение, но преобразует объекты домена в туннель POJO для сохранения. Туннель может быть реализован в любом пакете. Таким образом, объекты домена (объекты домена + репозитории) и постоянство (туннели) могут быть полностью разделены, а пакеты домена становятся чистым набором объектов памяти.

Архитектура развертывания в моделях предметной области

Операции Hema сильно взаимосвязаны. От совершения покупок у поставщиков до доставки продукции клиентам - отношения между объектами ясны. В принципе, можно использовать большую, полностью инклюзивную модель предметной области. Другой вариант - использовать boundedContext для разделения доменов на поддомены, как показано ниже на одной из иллюстраций эксперта DDD Мартина Фаулера:

Для таких экспертов Hema, как Huizi, идеальной структурой развертывания является:

Резюме

Таким образом, с помощью DDD Hema исследует широкие возможности проектирования архитектуры кодирования. Новый бизнес-режим Hema 2B + Internet Business предлагает множество деталей, заслуживающих тщательного изучения. DDD продемонстрировала отличную начальную производительность для Hema, решив реальные проблемы масштабируемости бизнеса и надежности системы. С помощью DDD команда Hema тщательно конструирует основанный на Интернете механизм распределенного рабочего процесса (Noble) и полностью основанный на Интернете механизм графического черчения (Ivy). В будущем инженеры Hema ожидают более уникального дизайна.

(Оригинальная статья Чжан Цюньхуэй 张群 辉)

Alibaba Tech

Подробная информация о новейших технологиях Alibaba из первых рук → Facebook: Alibaba Tech. Twitter: « AlibabaTech ».

Эта статья опубликована в The Startup, крупнейшем предпринимательском издании Medium, за которым следят + 380 756 человек.

Подпишитесь, чтобы получать наши главные новости здесь.