Skip to main content

Command Palette

Search for a command to run...

DDD архитектура на практике

Published
5 min read

Введение

Начнем серию постов про разработку IT продукта с базовых определений, и сегодня поговорим про domain driven design. Придерживаясь именно этих принципов я буду строить свое приложение.

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

Определение

DDD - domain driven design или предметно-ориентированное программирование. Это не технология, это набор принципов. DDD обязывает разработчика создавать абстракции, в которые входит бизнес логика. По сути это связующее звено между продуктом (бизнесом) и программным кодом.

Краткое описание

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

Зачем нужен этот подход?

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

Domain

DDD — подход к разработке, основанный на выделении логики в domain (домены). Домен — предметная область, для которой ведется разработка. Например, у бухгалтеров есть свои термины и важные понятия, которые отличаются от терминов, используемых в банковской сфере или сфере общепита. Эти термины отражают важные для заказчика процессы, связи, сущности. Именно поэтому важно знать их и уделять внимание терминам, которые используются в данной сфере. Разработка не должна быть в отрыве от бизнес-задач.

Ubiquitous language

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

Bounded Context

Bounded Context (ограниченный Контекст) — это некая граница, в которой существует модель предметной области. На основании контекста код разделяют на папки/файлы/пакеты/компоненты таким образом, чтобы изменения в каждом из них не оказывали (или оказывали минимальное) влияние на другие компоненты системы. Разработчикам это помогает эффективно работать в команде (один человек правит одну модель, второй параллельно другую. при слиянии изменений конфликтов быть не должно) и смелее вносить изменения в код, не боясь что где-то в другом месте, другая модель будет затронута/сломана.

Пример

Для примера, представим что у нас есть проект простого блога, в котором есть следующие сущности:

  • Post (модель поста). Может быть черновиком. Может быть лайкнут. У поста могут быть реакции. Посты хранят в себе количество просмотров
  • Author (модель пользователя). Хранит почту, имя, фамилию, позволяет их менять.

У наших сущностей будут бизнес методы (addLike, removeLike, addReaction, и т.д.), тогда мы смело можем сказать что принцип Ubiquitous language соблюден. И в разговоре с заказчиком, говоря слово post мы будем иметь именно нашу сущность с набором методов, а не пост из инстаграмма.

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

Есть несколько вариантов хранения кода:

1) По функциональному назначению. В файле usecase.file хранится все бизнес-логика для постов и для авторов, включая моки. В файле repo.file хранится вся логика, связанная с базой данных, для постов и для авторов, включая различные базы данных. В transport.file хранится вся транспортная логика для постов и авторов (включая разные транспорты: http hadnlers / grpc / graphql и т.д.). Минус такого подхода в том, что при растущем количестве сущностей, эти файлы станут очень большими, в них будет неудобно ориентироваться, и при командной разработке скорее всего будут конфликты слияния, когда несколько разработчиков внесут изменения в один файл.

usecase.file
repo.file
transport.file

2) Смесь функционального назначения и DDD. Тут мало того что для каждой сущности есть свой файл, так еще и файлы сгруппированы по своему логическому смыслу. usecase отдельно, transport отдельно, repo отдельно. Ориентироваться тут значительно проще. Шанс получить конфликт при слиянии веток намного меньше. Но это все еще не чистый DDD. Плюс файлы довольно объемные (например post_repo.file может хранить в себе реализации репозитория для mysql, sqlite, моки для тестов, кеш и много чего еще).

usecase
    post_usecase.file
    author_usecase.file
repo
    post_repo.file
    author_repo.file
transport
    post_transport.file
    author_transport.file

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

post
    transport.file
    usecase.file
    repo.file
author
    transport.file
    usecase.file
    repo.file

4) DDD + чистая архитектура. При таком расположении файлов и папок мы полностью соблюдаем DDD и избегаем циклических зависимостей. Я добавил несколько новых файлов в post, что бы показать зачем нужна такая группировка. У нас есть 2 домена: post и author. Эти сущности соблюдают контекст: изменения в author не затрагивают post и наоборот. Разработчики могут параллельно вносить в них изменения. Внутри каждой сущности есть подпапки, в которых по логике сгруппированы файлы: в post/transport можно положить все, что связано с транспортом для post (http, grpc, rest), в post/usecase кладем всю бизнес логику постов и ее моки для тестов, в post/repo будет вся логика для постов, которая касается баз данных и т.д.

post
    transport
        transport_http.file
        transport_grpc.file
        transport_graphql.file
    usecase
        usecase.file
        usecase_mock.file
    repo
        repo_mysql.file
        repo_sqlite.file
        repo_mock.file
author
    transport
        transport_http.file
    usecase
        usecase.file
    repo
        repo_mysql.file

Заключение

Не стоит воспринимать эту статью как единственную верную истину. Все зависит от проекта. Я показал примерное расположение файлов и папок, которое я делаю в своих проектах, которое я считаю за DDD. Если у вас небольшой проект (~2000 строк кода), можно вообще не дробить его не файлы и написать все в одном (в языке go). Так же я упомянул чистую архитектуру. Эта тема выходит за рамки данной статьи, о ней пишут книги и бесконечно спорят в комментариях. Я постараюсь в рамках похожей статьи немного осветить чистую архитектуру.

More from this blog

Чистая архитектура. Часть 1. SOLID

Чистая архитектура это Чистая архитектура — это категория шаблонов проектирования программного обеспечения для архитектуры программного обеспечения, которая следует концепциям чистого кода и реализует принципы SOLID. О них и пойдет речь в данной стат...

Mar 25, 20223 min read2.8K

Настраиваем свой собственный VPN

Тема довольно актуальная, решил описать как можно арендовать сервер и установить туда VPN, для доступа ко всем ресурсам :) Что такое VPN VPN - это виртуальная сеть, которая защищает ваше интернет-соединение от слежки и цензуры. Я намеренно опустил ...

Mar 17, 20227 min read285
Настраиваем свой собственный VPN

Задачи с Leetcode #2. Квадраты отсортированного массива

Описание Дан массив целых чисел, отсортированный по возрастанию. Необходимо вернуть массив из квадратов каждого числа, отсортированного по возрастанию. Примеры Исходные данныеРезультат выполненияОбъяснение -4,-1,0,3,100,1,9,16,100После возведе...

Mar 10, 20223 min read3.2K

Задачи с Leetcode #1. Бинарный поиск

Бинарный поиск Определение Двоичный (бинарный) поиск (также известен как метод деления пополам или дихотомия) — классический алгоритм поиска элемента в отсортированном массиве, использующий дробление массива на половины. Источник Wikipedia Алгоритм...

Mar 10, 20227 min read5.7K

Pavel Kozlov

5 posts

Golang developer, 26 y.o. Russia. Moscow