Практика проектирования на основе предметной области Alibaba Hema

задняя часть база данных модульный тест дизайн

предисловие

Дизайн — палка о двух концах: нет ни лучшего, ни лучшего, но все дороги ведут в Ханчжоу. В то же время есть проблема с отсутствием проектирования и перепроектированием.Правильный дизайн — это конечная цель, к которой мы стремимся.

DDD (Domain-Driven Design, Domain-Driven Design) — это просто жанр, не говоря уже о подавляющем, не говоря уже о совершенном. Чем я хочу поделиться с вами, так это обращаем ли мы внимание на сам дизайн, независимо от того, какой жанр дизайна, хорошо ли иметь дизайн.

Судя по коду, который я видел, большая часть кода в Ali Group не относится к типу DDD, и не так много конструкций, больше похожих на «код лапши», от конца строки до базы данных для завершения операции, Только некоторые проекты ориентированы на базу данных. Мы полагаемся на серьезное тестирование, чтобы гарантировать внешнее качество нашего программного обеспечения (престижность настойчивым тестировщикам), в то время как внутреннее качество неоднократно недооценивается в течение плотных проектных циклов, погрязших в ежедневных технических долгах.

Я всегда хотел написать что-нибудь, чтобы привлечь внимание каждого к дизайну, но я не знаю, что написать. Я перешел на Hema в прошлом году, и у меня было больше возможностей писать код и создавать систему с нуля. Хема отличается от большинства предприятий группы.Бизнес Хемы больше ориентирован на B-конец.От поставок до дистрибьюторской цепочки целостность очень сильна.Отношения сложные, если в них четко не разобраться, никто не может понять, что произошло. так вотдизайн имеет значение, код, который не разработан, не умрет сегодня или умрет завтра.Сколько бы мы ни оставались в Хеме, мы не можем вырыть яму для будущих братьев. В модуле, за который я отвечаю, мыПолностью примените DDD, чтобы завершить всю систему, который включает в себя наше собственное мышление и изменения, здесь я хочу поделиться с вами, камень других гор может атаковать нефрит, и каждый может извлечь из него уроки.

Обсуждение модели предметной области

1. Дизайн модели предметной области: на основе базы данных или на основе объектов

Обычно мы начинаем с двух измерений в дизайне:

  • Data Modeling: абстрагирование системных отношений через данные, то есть проектирование базы данных.
  • Object Modeling: Абстрагирование системных отношений посредством объектно-ориентированного проектирования, то есть объектно-ориентированного проектирования. Большинство архитекторов начинают проектирование программных систем с моделирования данных, и небольшое количество людей начинает проектирование программных систем с помощью объектного моделирования. Эти два метода моделирования не противоречат друг другу и оба важны, но то, с какого направления начинать проектирование, имеет большое значение для конечной формы системы.

Data Model

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

В команде разработчиков очень важен архитектор, он определяет структуру программного обеспечения, эта структура определяет будущую читабельность, масштабируемость и развиваемость программного обеспечения. Вообще говоря, архитекторы проектируют модель предметной области, а разработчики разрабатывают на основе этой модели предметной области. "Модель предметной области" - это модный термин. Если вернуться на 10 лет назад, мы назовем эту модель "словарем данных". Грубо говоря,Модель предметной области — это проектирование базы данных.

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

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

На сервисном уровне мы используем наш любимый менеджер для управления большей частью логики. POJO (модель потери крови будет обсуждаться позже) постоянно трансформируются и объединяются как данные в руке менеджера (руке Бога). Огромная фабрика обработки (очень тяжелый слой) завершает бизнес-логику вокруг ДНК базы данных.

Чтобы привести неуместный пример: если есть две таблицы, отец и сын, сгенерированный POJO должен быть:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{…}
public class Son{
    private String fatherId;//son 表里有 fatherId 作为 Father 表 id 外键
    public String getFatherId(){
        return fatherId;
    }
    ……
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

В это время сын сделал несколько ошибок, и отец очень расстроился, ударив сына, у отца болит рука, а у сына болит лицо. Менеджеры обычно делают это:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class SomeManager{
    public void fatherSlapSon(Father father, Son son){
        // 如果逻辑上说不通,大家忍忍
        father.setPainOnHand();
        son.setPainOnFace();// 假设 painOnHand, painOnFace 都是数据库字段
    }
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

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

Object Model

В 2004 году Эрик Эванс опубликовал «Дизайн, ориентированный на предметную область – Решение проблем в основе программного обеспечения» (Domain-Driven Design), именуемый Эвансом DDD, я бы хотел порекомендовать эту книгу всем здесь, теоретическое изложение.

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

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

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

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

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

Давайте посмотрим на пример гневного обмахивания Лао-цзы своего сына:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{
    // 教训儿子是自己的事情,并不需要别人帮忙,上帝也不行
    public void slapSon(Son son){
        this.setPainOnHand();
        son.setPainOnFace();
    }
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

В соответствии с этой идеей мы медленно проектировали реалистичные доменные модели в объектно-ориентированном мире, а сервисный уровень — это бизнес-операции, основанные на этих моделях (он становится тоньше, и многие действия передаются для обработки доменным объектам): домен Модель не завершает бизнес, каждый объект домена должен выполнять свое собственное должное поведение (единая ответственность), точно так же, как действие бегущего человека, person.run - это поведение, не связанное с бизнесом, но в это время менеджер или служба зовет Некоторые person.run могут доделать дело по бегу на 100 метров, а также могут доделать дело по бегу по доставке еды. В результате получается схема архитектуры, подобная следующей:

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

  • реальный: Сохранение данных объекта на носителе.
  • Выбирать: эффективно возвращает запросы данных в память.

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

Здесь я хочу подчеркнуть для вас:

  • Модель предметной области используется для операций с предметной областью, и, конечно, ее также можно использовать для запроса (чтения), но у этого запроса есть цена. При этом агрегат может содержать ряд данных, которые не подходят для диверсифицированных запросов, за исключением методов, подобных getById, а доменно-ориентированное проектирование не предназначено для диверсифицированных запросов.
  • Запрос основан на базе данных, фактически все сложные и ненормальные запросы должны обходить уровень домена и иметь дело непосредственно с базой данных.
  • Еще раз упростим: доменная операция -> объекты, запрос данных -> строки таблицы

2. Модели предметной области: кровопотеря, анемия, гиперемия.

Модели кровопотери, анемии, застоя и вздутия живота должны быть предложены Лао Ма (этот Лао Ма не учитель, а Мартин Фаулер), в котором рассказывается, как определить модель на основе полноты модели предметной области, которая представляет собой немного похоже: тонкий, средний, сильный и толстый. Раздутая (толстая) модель слишком толстая, поэтому мы не будем ее здесь обсуждать.

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

Модель анемии: Это неправильно, если сын не знает, кто его отец.Он не может найти своего отца каждый раз через тестовую ДНК промежуточного агентства (Менеджера) (son.fatherId), модель домена может быть немного богаче, и модифицировать сын класс:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Son{
    private Father father;
    public Father getFather(){return this.father;}
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

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

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{
    private Son son;
    private Son getSon(){return this.son;}
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

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

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__Son someSon = sonRepo.getById(12345);__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

Этот метод может удалить объект-сын из базы данных.Для создания полного объекта-сына в sonRepo требуется отцовский репозиторий для создания отца для назначения son.father. И отецРепо нуждается в сынеРепо, чтобы построить сына, чтобы назначить отца.сына при построении полного отца. Это формирует ненаправленный цикл, и проблема вызова цикла может быть решена, но для решения этой проблемы модель предметной области станет немного тошнотворной и бесполезной. Направленный ациклический вызов — наша цель. Чтобы предотвратить этот циклический вызов, можем ли мы Опустить ссылку из классов Отец и Сын? Измените класс отца:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{
    //private Son son; 删除这个引用
    private SonRepository sonRepo;// 添加一个 Son 的 repo
    private getSon(){return sonRepo.getByFatherId(this.id);}
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

Таким образом, при конструировании Отца мы не будем конструировать еще одного Сына, но цена будет заключаться в том, что мы введем SonRepository в класс Отца, то есть мы сошлемся на персистентную операцию в объекте предметной области, которую мы называем моделью перегрузки. .

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

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Shop{
    //private List<Product> products; 这个商品列表在构建时太大了
    private ProductRepository productRepo;
    public List<Product> getProducts(){
        //return this.products;
        return productRepo.getShopProducts(this.id);
    }
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

3. Модель предметной области: внедрение зависимостей

Кратко о внедрении зависимостей:

  • Внедрение зависимостей — это одноэлементный объект во время выполнения. Только объекты (@Component) в диапазоне, сканируемом spring, могут использовать внедрение зависимостей через аннотацию (@Autowired).Объекты, созданные с помощью new, не могут быть внедрены через аннотацию.
  • Лично рекомендую инъекцию зависимостей конструктора, в данном случае удобную для тестирования, хорошую целостность конструкции объекта и явно сообщать вам, какой объект вы должны имитировать/заглушить.

После разговора о внедрении зависимостей давайте прямо сейчас посмотрим на модель перегрузки:

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{
    private SonRepository sonRepo;
    private Son getSon(){return sonRepo.getByFatherId(this.id);}
    public Father(SonRepository sonRepo){this.sonRepo = sonRepo;}
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

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

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__@Component
public class FatherFactory{
    private SonRepository sonRepo;
    @Autowired
    public FatherFactory(SonRepository sonRepo){}
    public Father createFather(){
        return new Father(sonRepo);
    }
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

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

4. Модель предметной области: удобство тестирования

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

__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__public class Father{
    private SonRepository sonRepo;//=new SonRepository() 这里不能构造
    private getSon(){return sonRepo.getByFatherId(this.id);}
    // 放到构造函数里
    public Father(SonRepository sonRepo){this.sonRepo = sonRepo;}
}__Mon Jan 22 2018 14:54:23 GMT+0800 (CST)____Mon Jan 22 2018 14:54:23 GMT+0800 (CST)__

Важность размещения SonRepository в конструкторе заключается в удобстве тестирования. Путем имитации/заглушки этого репозитория модульное тестирование может быть успешно завершено.

5. Модель домена: реализация репозитория в режиме Hema

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

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

6. Модель предметной области: архитектура развертывания

Бизнес Хемы отличается высокой целостностью: от закупок у поставщиков до доставки продуктов пользователям, взаимосвязь между объектами относительно ясна. В принципе, можно использовать большую и всеобъемлющую модель предметной области или использовать boundedContext для дизассемблирования молекулярной области и обработки данных. передача на развязке, вот фото Лао Ма:

Я лично предпочитаю подход с большим доменом, и моя предпочтительная (так что на самом деле это не так) структура развертывания:

Эпилог

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

об авторе

Чжан Цюньхуэй, директор Alibaba Hema Architecture. Обладая более чем 10-летним практическим опытом в области технологий и управления, бывший директор по инженерной эффективности бизнес-подразделения Alibaba по инфраструктуре долгое время руководил проектированием архитектуры больших и сложных систем на переднем крае. Один из первых практиков DevOps, микросервисной архитектуры и доменно-ориентированного дизайна в Китае. Выступая за практику истинного знания, стремился быть на переднем крае технологий.

благодарныйЮтада ХикаруПланирование и рецензирование этой статьи.