luna_park 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.overcommit.yml +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +106 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +308 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +182 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +30 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +1 -0
- data/docs/README.md +190 -0
- data/docs/_coverpage.md +18 -0
- data/docs/_imgs/adapter.png +0 -0
- data/docs/_imgs/bender.jpeg +0 -0
- data/docs/_imgs/bender_header.jpeg +0 -0
- data/docs/_imgs/collecting.png +0 -0
- data/docs/_imgs/conductor_schema.png +0 -0
- data/docs/_imgs/ddd_header.jpeg +0 -0
- data/docs/_imgs/ddd_map.png +0 -0
- data/docs/_imgs/domain_context.jpeg +0 -0
- data/docs/_imgs/drunk_master.jpg +0 -0
- data/docs/_imgs/full_map.png +0 -0
- data/docs/_imgs/full_map_hight_res.png +0 -0
- data/docs/_imgs/graph_1.png +0 -0
- data/docs/_imgs/graph_2.jpg +0 -0
- data/docs/_imgs/graph_3.png +0 -0
- data/docs/_imgs/graph_4.jpg +0 -0
- data/docs/_imgs/graph_5.jpg +0 -0
- data/docs/_imgs/graph_5.png +0 -0
- data/docs/_imgs/processing.png +0 -0
- data/docs/_imgs/representation.png +0 -0
- data/docs/_imgs/storage.png +0 -0
- data/docs/_imgs/tourtle_context_map.png +0 -0
- data/docs/_imgs/tree.png +0 -0
- data/docs/_imgs/wm.jpeg +0 -0
- data/docs/_media/bender.jpg +0 -0
- data/docs/_media/black_cover.jpg +0 -0
- data/docs/_media/logo.svg +7 -0
- data/docs/_sidebar.md +9 -0
- data/docs/architecture.md +214 -0
- data/docs/google48f1e6f5c35eae5f.html +1 -0
- data/docs/index.html +32 -0
- data/docs/methodology.md +376 -0
- data/docs/patterns/entity.md +193 -0
- data/docs/patterns/sequence.md +332 -0
- data/docs/patterns/service.md +280 -0
- data/docs/patterns/value.md +197 -0
- data/docs/way.md +66 -0
- data/lib/luna_park.rb +72 -0
- data/lib/luna_park/callable.rb +7 -0
- data/lib/luna_park/entities/attributable.rb +18 -0
- data/lib/luna_park/entities/nested.rb +28 -0
- data/lib/luna_park/entities/simple.rb +39 -0
- data/lib/luna_park/errors.rb +16 -0
- data/lib/luna_park/errors/base.rb +244 -0
- data/lib/luna_park/errors/business.rb +9 -0
- data/lib/luna_park/errors/http.rb +90 -0
- data/lib/luna_park/errors/json_parse.rb +11 -0
- data/lib/luna_park/errors/system.rb +9 -0
- data/lib/luna_park/extensions/attributable.rb +26 -0
- data/lib/luna_park/extensions/callable.rb +44 -0
- data/lib/luna_park/extensions/comparable.rb +90 -0
- data/lib/luna_park/extensions/comparable_debug.rb +96 -0
- data/lib/luna_park/extensions/data_mapper.rb +195 -0
- data/lib/luna_park/extensions/dsl/attributes.rb +135 -0
- data/lib/luna_park/extensions/dsl/foreign_key.rb +97 -0
- data/lib/luna_park/extensions/exceptions/substitutive.rb +83 -0
- data/lib/luna_park/extensions/has_errors.rb +125 -0
- data/lib/luna_park/extensions/injector.rb +189 -0
- data/lib/luna_park/extensions/injector/dependencies.rb +74 -0
- data/lib/luna_park/extensions/predicate_attr_accessor.rb +23 -0
- data/lib/luna_park/extensions/repositories/postgres/create.rb +20 -0
- data/lib/luna_park/extensions/repositories/postgres/delete.rb +15 -0
- data/lib/luna_park/extensions/repositories/postgres/read.rb +63 -0
- data/lib/luna_park/extensions/repositories/postgres/update.rb +21 -0
- data/lib/luna_park/extensions/serializable.rb +99 -0
- data/lib/luna_park/extensions/severity_levels.rb +120 -0
- data/lib/luna_park/extensions/typed_attr_accessor.rb +26 -0
- data/lib/luna_park/extensions/validatable.rb +80 -0
- data/lib/luna_park/extensions/validatable/dry.rb +24 -0
- data/lib/luna_park/extensions/wrappable.rb +43 -0
- data/lib/luna_park/forms/simple.rb +63 -0
- data/lib/luna_park/forms/single_item.rb +74 -0
- data/lib/luna_park/handlers/simple.rb +17 -0
- data/lib/luna_park/http/client.rb +328 -0
- data/lib/luna_park/http/request.rb +225 -0
- data/lib/luna_park/http/response.rb +381 -0
- data/lib/luna_park/http/send.rb +103 -0
- data/lib/luna_park/mappers/simple.rb +92 -0
- data/lib/luna_park/notifiers/bugsnag.rb +48 -0
- data/lib/luna_park/notifiers/log.rb +174 -0
- data/lib/luna_park/notifiers/sentry.rb +50 -0
- data/lib/luna_park/repositories/postgres.rb +38 -0
- data/lib/luna_park/repositories/sequel.rb +11 -0
- data/lib/luna_park/repository.rb +9 -0
- data/lib/luna_park/serializers/simple.rb +28 -0
- data/lib/luna_park/tools.rb +19 -0
- data/lib/luna_park/use_cases/scenario.rb +325 -0
- data/lib/luna_park/use_cases/service.rb +13 -0
- data/lib/luna_park/validators/dry.rb +67 -0
- data/lib/luna_park/values/attributable.rb +21 -0
- data/lib/luna_park/values/compound.rb +26 -0
- data/lib/luna_park/values/single.rb +35 -0
- data/lib/luna_park/version.rb +5 -0
- data/luna_park.gemspec +54 -0
- data/node_modules/.yarn-integrity +12 -0
- data/package-lock.json +3 -0
- data/yarn.lock +4 -0
- metadata +414 -0
data/docs/_coverpage.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
![logo](_media/black_cover.jpg)
|
2
|
+
|
3
|
+
# LunaPark <small>0.5.9</small>
|
4
|
+
|
5
|
+
> Simple Domain-Driven-Design ruby framework
|
6
|
+
|
7
|
+
- Make complex things simple
|
8
|
+
- Knowledge is more important than technology
|
9
|
+
- Pragmatism is more important than dogmatism
|
10
|
+
- Hardness in architecture, flexibility in solutions
|
11
|
+
|
12
|
+
[GitHub](https://github.com/am-team/luna_park/)
|
13
|
+
[Guideline](#Область-применения)
|
14
|
+
|
15
|
+
Contacts:
|
16
|
+
- Alexander Kudrin - [alexander.kudrin@lunapark.dev](mailto:alexander.kudrin@lunapark.dev)
|
17
|
+
- Philipp Sorokin - [philipp.sorokin@lunapark.dev](mailto:philipp.sorokin@lunapark.dev)
|
18
|
+
- Telegram - https://t.me/lunapark_dev
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/docs/_imgs/tree.png
ADDED
Binary file
|
data/docs/_imgs/wm.jpeg
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
4
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
5
|
+
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
|
6
|
+
<g><path d="M500,10c28.5,0,51.6,23.1,51.6,51.6c0,21.2-19.2,39.4-37.5,47.3l13.8,133.2c98.7,1.2,178.4,81.5,178.4,180.5l0,77.4h-25.8c57,0,103.2,46.2,103.2,103.2v51.6c0,57-46.2,103.2-103.1,103.2h25.8v196C659.3,975.8,584.4,990,500,990s-159.3-14.2-206.3-36.1v-196h25.8c-57,0-103.1-46.2-103.1-103.2v-51.6c0-57,46.2-103.2,103.2-103.2h-25.8v-77.4c0-99,79.7-179.4,178.4-180.5l13.8-133.2c-18.3-7.9-37.5-26.1-37.5-47.3C448.4,33.1,471.5,10,500,10z M680.5,525.8H319.5c-42.7,0-77.4,34.6-77.4,77.4v51.6c0,42.7,34.6,77.4,77.4,77.4h361.1c42.7,0,77.4-34.6,77.4-77.4v-51.6C757.9,560.4,723.3,525.8,680.5,525.8z M500,790.1l-25.8-6.1v51.2h51.6v-51.2L500,790.1z M680.5,835.3c0-51.6-19.7-43.3-51.6-51.7v51.7H680.5z M551.6,785.3v50h51.6v-50.7L551.6,785.3z M396.8,784.5v50.7h51.6v-50L396.8,784.5z M319.5,835.3h51.6v-51.7C339.1,792,319.5,783.7,319.5,835.3z M500,912.6l25.8-0.6v-51h-51.6v51L500,912.6z M671.4,861.1h-42.5v30.8C649.4,883.1,663.3,872.3,671.4,861.1z M551.6,861.1v49.1c19.6-2,36.8-5.3,51.6-9.4v-39.7H551.6z M396.8,861.1v38.7h-3.6c15.6,4.6,33.9,8.2,55.1,10.4v-49.1H396.8z M328.6,861.1c8.2,11.3,22.1,22,42.5,30.8v-30.8H328.6z M371.1,603.2h51.6v51.6h-51.6V603.2z M577.4,603.2h51.6v51.6h-51.6V603.2z M319.5,551.6h30.8c-26.2,15.8-43.7,44.5-43.7,77.4c0,32.8,17.5,61.6,43.7,77.4h-30.9c-28.5,0-51.6-23.1-51.6-51.6v-51.6C267.9,574.7,291,551.6,319.5,551.6z M680.5,551.6c28.5,0,51.6,23.1,51.6,51.6v51.6c0,28.5-23.1,51.6-51.6,51.6h-30.8c26.2-15.8,43.7-44.5,43.7-77.4c0-32.8-17.5-61.6-43.7-77.4H680.5z M443.4,706.3c26.2-15.8,43.7-44.5,43.7-77.4c0-32.8-17.5-61.6-43.7-77.4h113.3c-26.2,15.8-43.7,44.5-43.7,77.4c0,32.8,17.5,61.6,43.7,77.4H443.4z"/></g>
|
7
|
+
</svg>
|
data/docs/_sidebar.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Архитектура
|
2
|
+
|
3
|
+
|
4
|
+
![WM](_imgs/wm.jpeg)
|
5
|
+
|
6
|
+
# Гибкая архитектура
|
7
|
+
DDD включает в себя практику реализации через модель. Предметная область должна описываться через ваш код. Давайте попробуем разобраться, как это сделать.
|
8
|
+
|
9
|
+
В своей книге Эрик Эванс приводит ряд шаблонов проектирования, рекомендуемых к использованию, и обозначает данный подход как гибкий:
|
10
|
+
|
11
|
+
> Во имя гибкости архитектуры в программах было нагромождено множество ненужных конструкций. Лишние уровни абстрагирования и косвенных ссылок чаще мешают, чем помогают в этом деле. Посмотрите на архитектуру, которая действительно вдохновляет программистов, занимающихся ее доработкой, и вы увидите, как правило, что-нибудь очень простое. Но простое - не значит легкое в исполнении. Чтобы создать такие элементы, которые можно собрать в сложные системы и при этом нетрудно понять, необходимо сочетать "преданность " проектированию по модели с достаточно строгим стилем архитектуры. Определенный навык проектирования нужен не только для создания чего-либо, но даже для использования готового.
|
12
|
+
|
13
|
+
> Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software
|
14
|
+
|
15
|
+
Представленный набор шаблонов проектирования не является строгой архитектурой или готовым решением, а, скорее, пищей для размышления.
|
16
|
+
|
17
|
+
# Кричащая архитектура
|
18
|
+
|
19
|
+
Похожие мысли возникали в голове у многих разработчиков и проектировщиков сложных систем.
|
20
|
+
|
21
|
+
В 2011 году вышла статья Роберта Мартина - [Screaming Architecture](https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html), которая говорит о том, что ваш код не просто должен описывать предметную область, а орать о ней, желательно матом.
|
22
|
+
|
23
|
+
> So what does the architecture of your application scream? When you look at the top level directory structure, and the source files in the highest level package; do they scream: Health Care System, or Accounting System, or Inventory Management System? Or do they scream: Rails, or Spring/Hibernate, or ASP?
|
24
|
+
|
25
|
+
> Robert C. Martin, 30 September 2011
|
26
|
+
|
27
|
+
Роберт рассказывает, что код вашего приложения должен отображать деятельность приложения, вместо того чтобы подстраиваться под правила фреймворка. Структура фреймворка не должна ограничивать вашу архитектуру. Приложение,в свою очередь, не должно привязываться к БД или http протоколу, это всего лишь механизмы хранения и доставки. Ограничительные рамки являются инструментом. Не следует становиться адептом фреймоворка. Тесты вашего приложения - это тесты логики его работы, а не тестирование http протокола.
|
28
|
+
|
29
|
+
# Чистая архитектура
|
30
|
+
|
31
|
+
Через год выходит следующая статья Роберта Мартина - [The Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html). В ней автор рассказывает, как добиться того, чтобы код кричал. Изучив несколько архитектур, он выделяет основные принципы:
|
32
|
+
|
33
|
+
1. Независимость от рамок. Архитектура не зависит от какой-то существующей библиотеки. Это позволяет использовать фреймворки как инструменты, а не ограничения, связывающие ваши руки.
|
34
|
+
2. Тестируемость. Бизнес-правила могут быть протестированы без пользовательского интерфейса, базы данных, веб-сервера или любого другого технического средства.
|
35
|
+
3. Независимость от пользовательского интерфейса. Пользовательский интерфейс может легко меняться, не изменяя остальную часть системы. Например, веб-интерфейс можно заменить консольным интерфейсом, не изменяя бизнес-логику.
|
36
|
+
4. Независимость от базы данных. Вы можете обменять Oracle или SQL Server на Mongo, BigTable, CouchDB или что-то еще. Логика вашего приложения не должна быть привязана к базе данных.
|
37
|
+
5. Независимость от воздействия внешней среды. На самом деле ваши бизнес-правила просто ничего не знают о внешнем мире.
|
38
|
+
|
39
|
+
На хабре уже опубликована очень хорошая статья [Заблуждения Clean Architecture](https://habr.com/company/mobileup/blog/335382/). Ее автор, @Jeevuz очень хорошо разжевал тонкости понимания данного подхода. Настоятельно рекомендую ознакомиться как и с ней так и с оригинальными материалами.
|
40
|
+
|
41
|
+
# Вариативная архитектура
|
42
|
+
|
43
|
+
Описание представленного выше подхода не выглядит столь однозначным. В рамках разработки архитектуры ряда сложных корпоративных систем мной и моими коллегами была выработана достаточно четкая интерпретация описанных подходов, которую я собираюсь изложить ниже.
|
44
|
+
|
45
|
+
До появления компьютеров и языков программирования при построении и управлении системами со сложной бизнес-логикой использовался бумажный документооборот. Результатом любого процесса являлся документ, который в конечном счете описывал тот или иной бизнес-объект. В итоге делопроизводство сводилось к трем простым _Действиям_:
|
46
|
+
|
47
|
+
1. Создание документа
|
48
|
+
2. Обработка документа
|
49
|
+
3. Работа с архивом документов
|
50
|
+
4. Представление документа
|
51
|
+
|
52
|
+
> Документ - фиксация информации о хозяйственной деятельности относительного того или иного реального бизнес-объекта.
|
53
|
+
|
54
|
+
Прошу заметить, что документ сам по себе не является реальным бизнес-объектом, а только его _Моделью_. В данный момент бумажные документы вытесняются электронными. Документом может быть запись в таблице, картинка, файл, отправленное письмо или любой другой фрагмент информации.
|
55
|
+
Я бы не хотел в дальнейшем использовать слово документ, так как оно будет вносить скорее путаницу, мы будем использовать понятие _Сущность_ (Entity) из DDD терминологии. Но вы можете представить, что сейчас вся ваша система, это система электронного документооборота, которая выполняет четыре простых _Действия_.
|
56
|
+
|
57
|
+
1. Collecting
|
58
|
+
2. Processing
|
59
|
+
3. Storage
|
60
|
+
4. Representation
|
61
|
+
|
62
|
+
> Действие (Action) - структурная единица деятельности бизнес-модели; относительно завершенный отдельный акт осознаваемой цели, произвольность и преднамеренность индивидуальной активности бизнес-объекта, различаемая конечным потребителем.
|
63
|
+
|
64
|
+
Хорошим примером _Дейсвтия_ является театральный акт. Театр моделирует события из реальной жизни. Акт является осмысленной частью пьесы. Но, чтобы сделать историю законченной, нужно проиграть несколько актов в строго отведенном порядке. Такой порядок в нашей архитектуре мы будем называть _Режим_.
|
65
|
+
|
66
|
+
> Режим (Conduction)- набор _Действий_ в определенном порядке, имеющий законченный смысл, несущий пользу конечному потребителю.
|
67
|
+
|
68
|
+
![conduction](_imgs/conductor_schema.png)
|
69
|
+
|
70
|
+
<a name="conduction" id="conduction"></a> Для подобных _Режимов_ работы был придуман селективный кондуктор или _Вариатор_ (Selector). Точнее "Timing mechanism for conducting a selected one of a plurality of sequences of operation", на который был получен патент [US2870278A](https://patents.google.com/patent/US2870278A/en). Мы знаем это устройство как "крутилка" стиральной машины. Архитектурная "крутилка" приведена в начале статьи.
|
71
|
+
|
72
|
+
Вариативность подхода проявляется в том, что с такой архитектурой вы можете выбрать любой из четырех _Режимов_, проходя которые вы не будете совершать лишних _Действий_.
|
73
|
+
|
74
|
+
Запуская стиральную машину, вы можете выбрать режим: стирка, полоскание или отжим. Если вы выбрали стирку, то ваша машина все равно прополощет белье, а затем его отожмет. С полоскание в комплекте вы обязательно получите отжим. Отжим - финальное _Действие_ в процессе стирки оно является самым “простым”. В нашей архитектуре самое простое _Дейтствие_ - _Представление_, с него и начнем.
|
75
|
+
|
76
|
+
## Представление (Representation)
|
77
|
+
|
78
|
+
Если говорить о чистом представлении без обращения к базе данных или внешнему источнику, то мы выдаем какую-то статическую информацию: html-страницу, файл, справочник лежащий в виде json'a. Мы даже можем выдать просто _Code response_ - 200:
|
79
|
+
|
80
|
+
Напишем простейший "Health checker"
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
module Health
|
84
|
+
class Endpoints < Sinatra::Base
|
85
|
+
get '/check' do; end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
В самом примитивном виде наша схема будет выглядеть так:
|
91
|
+
|
92
|
+
![Representation](_imgs/representation.png)
|
93
|
+
|
94
|
+
<spoiler title="Лирическое отступление">
|
95
|
+
Я прошу заметить, что во фреймворке Sinatra класс _Endpoints_ объединяет в себе как _Router_, так и _Controller_ в одном классе. Не нарушает ли это принципа единственной ответственности? По факту, Endpoints это не класс, а слой, выраженный через класс, и зона его ответственности на более высоком уровне.
|
96
|
+
|
97
|
+
Ок, а как же _Router_ и _Controller_? Они представлены не набором классов, а наименованием и реализацией функции. А статический файл это вообще файл. Один класс отвечает одной ответственности, но не пытайтесь выразить каждую ответственность через класс. Исходите из практичности, а не из догматизма.
|
98
|
+
</spoiler>
|
99
|
+
|
100
|
+
## Работа с системой хранения (Storage)
|
101
|
+
|
102
|
+
Бизнес требователен к доступности вашего приложения. Зачем кому-то нужен ваш сервис, если в нужный момент мы не можем его использовать? Для обеспечения целостности данных мы фиксируем изменение состояния бизнес-объекта после каждой обработки.
|
103
|
+
|
104
|
+
Чтобы извлечь объект из хранилища, не требуется обращение к бизнес-логике. Представим, что мы автоматизируем деятельность сети отелей и у нас есть журнал постояльцев на стойке регистрации. Мы решили посмотреть информацию о посетителе.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
module Reception
|
108
|
+
class Endpoints < Sinatra::Base
|
109
|
+
|
110
|
+
# Show item
|
111
|
+
get '/residents/:id', provides: :json do
|
112
|
+
resident = Repository::Residents.find params[:id]
|
113
|
+
status 200
|
114
|
+
serialize(resident)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
_Работа с системой хранения_ в виде графической схемы:
|
121
|
+
|
122
|
+
![Storage](_imgs/storage.png)
|
123
|
+
|
124
|
+
Как мы можем заметить общение между уровнем, отвечающим за _Хранение_, и уровнем, отвечающим за представление данных, реализовано через Response model. Данная модель не принадлежит ни одному из этих слоев. По факту, это бизнес-объект и он находится на слое, отвечающим за бизнес-логику.
|
125
|
+
|
126
|
+
## Обработка (Processing)
|
127
|
+
|
128
|
+
Если речь заходит о том, что объектная модель изменяется на основе своих свойств без внесения новых данных, то мы обращаемся к слою _Интерактора_ напрямую. Слой _Интерактора_ является ключевым в нашем приложении, именно в нем описывается вся бизнес-логика в виде отдельных _Вариантов использования_ (Use Cases) и именно на нем идет изменение _Сущностей_.
|
129
|
+
|
130
|
+
Рассмотрим такой сценарий использования. В нашей гостинице посетитель уже зарегистрирован, но мы отмечаем каждый его приход или уход.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
module Reception
|
134
|
+
class Endpoints < Sinatra::Base
|
135
|
+
|
136
|
+
# Register resident arrival
|
137
|
+
post '/residents/:uid/arrival', provides: :json do
|
138
|
+
result = Interactors::Arrival.call(resident_id: params[:id])
|
139
|
+
check!(result) do
|
140
|
+
status 201
|
141
|
+
serialize result.data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Register resident departure
|
146
|
+
post '/residents/:uid/departure', provides: :json do
|
147
|
+
result = Interactors::Departure.call(resident_id: params[:id])
|
148
|
+
check!(result) do
|
149
|
+
status 201
|
150
|
+
serialize result.data
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
Давайте немного остановимся. Почему не сделать реализацию одним методом с параметром `status` ? _Интеракторы_ `Arrival` и `Departure` в корне отличаются. Если к нам пришел постоялец, то мы должны проверить закончилась ли уборка, не поступало ли для него новых сообщений и т.п. При его уходе мы, наоборот, должны инициировать уборку в случае необходимости. Про сообщения, в свою очередь, мы даже не вспоминаем, поскольку если бы он был в гостинице, мы бы ему сразу позвонили. Именно всю эту логику бизнеса мы и прописываем на слое _Интерактора_.
|
158
|
+
|
159
|
+
![Processing](_imgs/processing.png)
|
160
|
+
|
161
|
+
Но что нам делать, если у нас есть данные извне? Тут подключается действие _Сбора данных_.
|
162
|
+
|
163
|
+
## Сбор данных (Collecting)
|
164
|
+
|
165
|
+
При первичной регистрации постояльца в гостинице он заполняет форму регистрации. Эта форма проверяется. Если данные верны, то происходит бизнес-процесс Регистрация. Процесс возвращает данные - созданную бизнес-модель "Постояльца". Эту модель мы представляем постояльцу в читаемой форме:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
module Reception
|
169
|
+
class Endpoints < Sinatra::Base
|
170
|
+
|
171
|
+
# Register new resident
|
172
|
+
post '/residents', provides: [:json] do
|
173
|
+
form = Forms::Registration.new(params)
|
174
|
+
submit! form do
|
175
|
+
check! form.result do
|
176
|
+
status 201
|
177
|
+
serialize form.result.data
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
Схематично это выглядит так:
|
186
|
+
|
187
|
+
![Collecting](_imgs/collecting.png)
|
188
|
+
|
189
|
+
# Правила игры (Rules)
|
190
|
+
|
191
|
+
- Вариативная система с точки зрения процессов делится на _Действия_.
|
192
|
+
- Последовательность _Действий_ определяется _Режимом_.
|
193
|
+
- _Режимы_ инкрементальны.
|
194
|
+
- Более "сложный" _Режим_ дополняет более "простой", на строго одно действие.
|
195
|
+
- Каждое действие происходит в рамках одного _Слоя_.
|
196
|
+
- Каждый слой представлен _Классом_.
|
197
|
+
- Внутри слоя могут быть _Классы-Слои_ и _Классы-Ответственности_.
|
198
|
+
- Общение происходит только между _Слоем_ и _Внутрислойным Классом_.
|
199
|
+
- _Модели-Представления_ являются исключениями.
|
200
|
+
- Обработка ошибок должна происходить на уровне _Класса-Cлоя_.
|
201
|
+
|
202
|
+
![Tree](_imgs/tree.png)
|
203
|
+
|
204
|
+
# Общая схема
|
205
|
+
|
206
|
+
У данного подхода высокий порог вхождения. Его применение требует от проектировщика большого опыта для четкого осознания решаемых задач. Сложность также представляет разнообразие выбора необходимого инструмента. Но, не смотря на сложность структуры, реализация на уровне кода невероятно проста и выразительна. Хотя и содержит в себе ряд условностей и доверенностей. В дальнейшем мы разберем каждый шаблон проектирования в отдельности, опишем как его создать, тестировать и обозначим область применения. А чтобы не запутаться в их многообразии, предлагается полная карта:
|
207
|
+
![Карта в высоком разрешении](_imgs/full_map_hight_res.png)
|
208
|
+
|
209
|
+
---
|
210
|
+
- Проблемно-ориентированное проектирование, Эрик Дж. Эванс
|
211
|
+
- Кейт Матсудейра: Масштабируемая веб-архитектура и распределенные системы
|
212
|
+
- https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
|
213
|
+
- https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html
|
214
|
+
- https://habr.com/company/mobileup/blog/335382/
|
@@ -0,0 +1 @@
|
|
1
|
+
google-site-verification: google48f1e6f5c35eae5f.html
|
data/docs/index.html
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Document</title>
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
+
<meta name="description" content="Description">
|
8
|
+
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
9
|
+
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div id="app"></div>
|
13
|
+
<script>
|
14
|
+
window.$docsify = {
|
15
|
+
name: 'LunaPark',
|
16
|
+
repo: 'am-team/luna_park',
|
17
|
+
loadSidebar: true,
|
18
|
+
coverpage: true,
|
19
|
+
auto2top: true,
|
20
|
+
maxLevel: 4,
|
21
|
+
subMaxLevel: 2,
|
22
|
+
ga: 'UA-131438707-1',
|
23
|
+
search: 'auto',
|
24
|
+
}
|
25
|
+
</script>
|
26
|
+
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
27
|
+
<script src="//unpkg.com/prismjs/components/prism-ruby.min.js"></script>
|
28
|
+
<script src="//unpkg.com/prismjs/components/prism-markdown.min.js"></script>
|
29
|
+
<script src="//unpkg.com/docsify/lib/plugins/ga.min.js"></script>
|
30
|
+
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
31
|
+
</body>
|
32
|
+
</html>
|
data/docs/methodology.md
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
# Методология
|
2
|
+
![ddd-header](_imgs/ddd_header.jpeg)
|
3
|
+
|
4
|
+
# DDD
|
5
|
+
|
6
|
+
Вспомним данное ранее определение:
|
7
|
+
|
8
|
+
> Проектирование на основе предметной области (DDD, Domain-driven design) - это подход к разработке программного обеспечения для комплексного удовлетворения потребностей, путем сильной связи реализации с основными бизнес-моделями, находящимися в процессе постоянного развития.
|
9
|
+
|
10
|
+
Эталонной книгой которая описывает практики построения сложных систем является книга [Domain Driven Dedign](https://domainlanguage.com/ddd/) (Big Blue Book) Эрика Эванса. Если вы читали любую обзорную статью по этой теме, вы уже знаете об этом. К моменту применения DDD на практике вам придется ее прочесть. Это не самая легкая книга для восприятия:
|
11
|
+
|
12
|
+
> The canonical source for DDD is Eric Evans's book. It isn't the easiest read in the software literature, but it's one of those books that amply repays a substantial investment.
|
13
|
+
>
|
14
|
+
> Martin Fowler: 15 January 2014
|
15
|
+
|
16
|
+
Если пролистать содержание книги, она покажется вам не совсем структурированной. Но нам поможет карта.
|
17
|
+
![DDD-map](_imgs/ddd_map.png)
|
18
|
+
|
19
|
+
На карте выделены те практики, которые мы рассмотрим.
|
20
|
+
|
21
|
+
Объем рассмотренных в книге практик огромен. Объем практик, которые можно применить за пределами данной книги еще больше. Прежде чем брать на вооружение хотя бы часть их них, обозначьте для себя цели. Приведу в качестве примера свои.
|
22
|
+
- Повысить производительность.
|
23
|
+
- Писать понятный код.
|
24
|
+
- Масштабирование на уровне разработки программного обеспечения.
|
25
|
+
|
26
|
+
|
27
|
+
# Единый язык
|
28
|
+
|
29
|
+
Разработка программного обеспечения редко приводит к созданию чего-то нового, как правило, это моделирование чего-то существующего.
|
30
|
+
|
31
|
+
> Модель - представление реального объекта, включающее в себя только необходимые свойства и функции.
|
32
|
+
|
33
|
+
Мы не можем создать программный продукт, который охватит всю предметную область. Возможно воспроизвести только ту его часть, которая будет воспроизводить необходимый функционал.
|
34
|
+
|
35
|
+
Хорошим примером модели будет являться топографическая карта. Она является моделью местности. Карта не содержит лугов полей и рек, она только отражает месторасположение реальных объектов относительно друг друга.
|
36
|
+
|
37
|
+
Чтобы построить четкую и ясную для всех модель, нужно говорить на одном языке. Об этом нам говорит не только Эрик Эванс, но и здравый смысл. Если программисты будут использовать свои термины, а бизнес свой 'сленг', то первые просто не будут понимать что нужно делать. Бизнес же в этом случае не сможет осознать реальной стоимости разработки той или иной 'фичи'. Сколько раз вам приходилось слышать: "Да это всего лишь добавить кнопку"?
|
38
|
+
|
39
|
+
Ваша цель, как проектировщика системы, должна быть в том, чтобы добиться от всей команды максимального понимания друг друга. Как же этого добиться? Начните говорить. Если люди начинают общаться в любой тесной группе, у них появляется некий общепринятый набор терминов. В различных компаниях процесс введения общего языка, скорее всего, будет отличаться. Это может быть как волевым решением, так и демократической процедурой. Язык может быть обозначен явно, так и вводиться не явно, в этом случае на нем начинают просто говорить. Хорошим приемом введения общего языка станет общая документация.
|
40
|
+
|
41
|
+
## Документация проекта
|
42
|
+
|
43
|
+
1. Любое общение между бизнесом и разработкой должно улучшить вашу модель.
|
44
|
+
2. После совещания зафиксируйте результат в виде документации (артефакт Scrum), и покажите эту документацию всем участникам процесса разработки.
|
45
|
+
3. Используйте в документации единый язык.
|
46
|
+
4. Самое важное: не тратьте время на документацию. Вам еще придется писать код, а документация будет переписываться многократно, тратить ресурсы - это дорого. Вместо того, чтобы долго возиться с приложением для рисования диаграмм UML, воспользуйтесь салфеткой, ручкой и фотоаппаратом на телефоне.
|
47
|
+
5. Документация требует дисциплины, нельзя писать ее время от времени.
|
48
|
+
6. Разделите документацию:
|
49
|
+
- Комментарии в коде - описывайте непонятные моменты прямо в коде, оставляйте `#ТODO:` (убирайте, когда сливаете код в master). Выражайте свое мнение в комментариях, к примеру вам приходиться использовать тот или иной костыль при работе с legasy кодом.
|
50
|
+
- Комментарии к проекту `README.md` в корневом каталоге вашего проекта должны содержать техническую информацию: как запустить проект, как прогнать тесты, и т.п. Так же неплохо завести карту, где у вас лежат все проекты, и на каких серверах они запущенны. Отдельно записать все принятые соглашения.
|
51
|
+
- И самое важное - база знаний. Сборник документов, описывающих бизнес процессы, это та часть документов, которая доступна как вам, так и бизнесу.
|
52
|
+
7. Основная ошибка тех, кто пишет документацию, избыточность. Не пытайтесь охватить все и вся, передавайте только общий смысл. Документация должна дополнять ваш проект, но никак не заменять его. Не записывайте все термины, только имеющие неоднозначное значение. Если определение занимает больше двух предложений, это плохое определение.
|
53
|
+
|
54
|
+
Пример документации:
|
55
|
+
|
56
|
+
```markdown
|
57
|
+
# Система авторизации
|
58
|
+
|
59
|
+
Система авторизации отвечает за идентификацию конкретного пользователя.
|
60
|
+
|
61
|
+
# Сущности:
|
62
|
+
|
63
|
+
Пользователь характеризуется:
|
64
|
+
- Именем и фамилией
|
65
|
+
- email
|
66
|
+
- телефоном
|
67
|
+
|
68
|
+
## Процессы:
|
69
|
+
|
70
|
+
### Регистрация
|
71
|
+
В процессе регистрации мы создаем нового пользователя, просим его подтвердить его email и телефон, авторизуем пользователя на 1 день (за это время он обязан подтвердить email и телефон).
|
72
|
+
|
73
|
+
### Авторизация
|
74
|
+
В процессе авторизации мы выписываем пользователю аутентификационный ключ на месяц. Аутентифицироваться может только пользователь с верифицированным телефоном и и email адресом.
|
75
|
+
|
76
|
+
### Верификация email
|
77
|
+
На email пользователя приходит письмо со ссылкой. Открывая ссылку пользователь сообщает, что это его email. Ссылка действительна сутки.
|
78
|
+
|
79
|
+
### Верификация телефона
|
80
|
+
На телефон пользователя приходит смс, ответив на которое он подтверждает, что это его телефон. Код действителен сутки.
|
81
|
+
|
82
|
+
### Восстановление пароля
|
83
|
+
Пользователь вводит свой email, на него приходит ссылка, по которой ему будет предложено изменить пароль. Ссылка действует 2 часа.
|
84
|
+
|
85
|
+
### Авторизация на других доменах
|
86
|
+
При использовании амортизационного ключа мы можем получить доступ к учетным записям на других доменах. Если ключ не подходит, происходит переход на страницу авторизации, при успешном прохождении которой будет произведен переход пользователя на исходную страницу исходного домена.
|
87
|
+
|
88
|
+
```
|
89
|
+
|
90
|
+
Заметим, что в данном примере мы не указали явный словарь, тем не менее мы закрепили понятие _Пользователь_, _Авторизация_, _Регистрация_. Написание подобной документации не займет у эксперта больше 20 минут.
|
91
|
+
|
92
|
+
Для человека, не являющегося экспертом в предметной области, процесс написания документации воспринимается как что-то сложное. Нужно разделять сбор знаний и запись собранных знаний. `Документирование != Документирование + Сбор знаний.`
|
93
|
+
|
94
|
+
>– То, что вы называете вселенной, – утверждал четвертый, – есть, собственно, скопление миров, которые, подобно кожице лука, находятся один на другом и постепенно отделяются друг от друга.
|
95
|
+
>
|
96
|
+
>– Необыкновенно ясно изложено! – восхищались абдериты. – Удивительно ясно! – Они полагали, что понимают философа, так как очень хорошо знали, что такое луковица.
|
97
|
+
>
|
98
|
+
> История Абердитов, Кристов Мартин Виланд
|
99
|
+
|
100
|
+
# Ограниченный контексты и домены
|
101
|
+
|
102
|
+
Представим себе, что мы выступили в роли проектировщика прогрессивного стартапа. Все мы любим остывшую пиццу, ругаться с курьерами и часами заполнять формы на сайте. Поэтому мы придумали замечательный стартап "Четыре черепахи и одна крыса":
|
103
|
+
- Есть сайт, на котором регистрируются пиццерии и вывешивают свои актуальные блюда.
|
104
|
+
- У данных пиццерий нет своей курьерской службы.
|
105
|
+
- Есть заказчики, которые не могут дойти до пиццерии, но они готовы сделать заказ через сайт или мобильное приложение.
|
106
|
+
- Киллер 'фича': курьерами выступают не специально нанятый персонал, а простые люди, зарегистрировавшиеся через мобильно приложение
|
107
|
+
- Курьеры получают заказы, после их исполнения им приходит оплата за проделанную работу.
|
108
|
+
- Ждать таких курьеров приходится дольше, но и доставка, а соответственно и пицца выходит дешевле.
|
109
|
+
|
110
|
+
Давайте вспомним документацию, которую мы описывали в предыдущей главе. Там в нашем едином словаре использовался термин _регистрация_. Но в данном проекте мы имеем их несколько:
|
111
|
+
- Регистрация заказчика
|
112
|
+
- Регистрация пиццерии
|
113
|
+
- Регистрация курьера
|
114
|
+
- Регистрация заказа
|
115
|
+
|
116
|
+
Унифицированный язык принадлежит к ограниченному контексту. Домен приведенной выше документации - 'Система авторизации'. Давайте попробуем выделить домены для нашего стартапа.
|
117
|
+
|
118
|
+
Но прежде чем мы к этому приступим, давайте немного разберемся в терминологии, что такое домен и что такое ограниченный контекст.
|
119
|
+
|
120
|
+
> Домен (Domain) - это репрезентация реальной бизнес структуры, решающая определенную задачу.
|
121
|
+
|
122
|
+
Например: Система логистики, Система оплаты, Система авторизации, Система управления заказами.
|
123
|
+
|
124
|
+
Домен делится на сабдомены, которые описывают меньшие структуры, например: корзина заказов, система построения маршрутов.
|
125
|
+
|
126
|
+
Каждый домен имеет ограниченную зону ответственности - лимитированную функциональность.
|
127
|
+
|
128
|
+
> Ограниченный контекст (Bounded context) - набор ограничений домена, помогающий сфокусироваться домену только на одной задаче для ее лучшего решения.
|
129
|
+
|
130
|
+
Мне нравится представлять этот термин в виде такой абстракции. Домен - это круг. Ограниченный контекст - это окружность.
|
131
|
+
|
132
|
+
![Domain-context](_imgs/domain_context.jpeg)
|
133
|
+
|
134
|
+
Еще в терминологии DDD выделяют ядро.
|
135
|
+
|
136
|
+
> Ядро (Core domain) - самый главный домен, наиболее емко характеризующий ваш бизнес.
|
137
|
+
|
138
|
+
Итак, домены проекта "Четыре черепахи и одна крыса":
|
139
|
+
|
140
|
+
__Работа с пиццерией__ (Pizzarias)
|
141
|
+
|
142
|
+
_Контекст_: b2b все, что относится к работе с пиццериями
|
143
|
+
|
144
|
+
_Сабдомены_:
|
145
|
+
- регистрация новых пиццерий
|
146
|
+
- добавление ассортимента
|
147
|
+
- обновления статуса наличия того или иного товара
|
148
|
+
|
149
|
+
__Работа с клиентом__ (Clients)
|
150
|
+
|
151
|
+
_Контекст_: b2c, все что относится к работе с заказчиками пиццы
|
152
|
+
|
153
|
+
_Сабдомены_:
|
154
|
+
- просмотр ассортимента
|
155
|
+
- информационные материалы
|
156
|
+
|
157
|
+
__Работа с курьерами__ (Delivery system)
|
158
|
+
|
159
|
+
_Контекст_: b2e, все что относится к работе с курьерами
|
160
|
+
|
161
|
+
_Сабдомены_:
|
162
|
+
- регистрация курьера
|
163
|
+
- выдача заданий
|
164
|
+
- регистрация заявок на вывод заработанных курьером средств.
|
165
|
+
|
166
|
+
__Система заказов__ (Order system)
|
167
|
+
|
168
|
+
_Контекст_: Ядро. Позволяет координировать все отдельные домены, обеспечивая полный цикл от получения заказа до доставки пиццы пользователю. Не является исполнителем, а исполняет роль дирижера.
|
169
|
+
|
170
|
+
_Сабдомены_:
|
171
|
+
- принятие заказа
|
172
|
+
- исполнение заказа
|
173
|
+
- отслеживание статуса заказа
|
174
|
+
|
175
|
+
__Система рассчетов__ (Billing)
|
176
|
+
|
177
|
+
_Контекст_: Содержит в себе все финансовые операции. Обеспечивает взаимодействие с процессинговым центром.
|
178
|
+
|
179
|
+
_Сабдомены_:
|
180
|
+
- прием денег за заказы
|
181
|
+
- выдача денег курьерам за выполненную работу
|
182
|
+
|
183
|
+
__Система статистки__ (Statistics)
|
184
|
+
|
185
|
+
_Контекст_: Сбор и обработка (не выдача) аналитической информации.
|
186
|
+
|
187
|
+
_Сабдомены_:
|
188
|
+
- статистики по средствам
|
189
|
+
- статистики по заявкам
|
190
|
+
|
191
|
+
__Система менеджмента__ (Managment panel)
|
192
|
+
|
193
|
+
_Контекст_: Выдача аналитической информации. Инструментарий управленческих решений.
|
194
|
+
|
195
|
+
- аналитика на основе собранной статистики
|
196
|
+
- премодерация выплат курьерам
|
197
|
+
|
198
|
+
На основе доменов давайте составим его карту.
|
199
|
+
|
200
|
+
> Карта доменов (Context map) - графический инструмент, который позволяет описать связи между отдельными доменами.
|
201
|
+
|
202
|
+
![Context-map](_imgs/tourtle_context_map.png )
|
203
|
+
|
204
|
+
Карта показывает связи между доменами. Данная карта очень поверхностна, но и предметная область изучена не достаточно. Это первый набросок, переписывая который вы получите ожидаемый результат.
|
205
|
+
|
206
|
+
Самое важное в карте - мы видим связи межу доменами. Такая структура очень хорошо ложится на микросервисную архитектуру:
|
207
|
+
|
208
|
+
> Главный принцип микросервисной архитектуры: слабая связанность и сильное сцепление.
|
209
|
+
|
210
|
+
Данный принцип дается в книге Сэма Ньюмана - [Создание микросервисов](http://shop.oreilly.com/product/0636920033158.do), это вторая книга, которую вам придется прочесть, чтобы приступить к практическому использованию описанных в данной статье подходов. Что имеется в виду: домены должны быть слабо связанны между собой, но тесно сцеплены внутри.
|
211
|
+
|
212
|
+
Перевод данных терминов взят из официального русского перевода и, возможно, плохо отражает передаваемый смысл. В оригинале термины звучат как: Low coupling (связанность, зацепление, сцепление, сопряженность), high cohesion (связность, прочность).
|
213
|
+
|
214
|
+
# Доменное разделение
|
215
|
+
|
216
|
+
Хотелось бы поделиться личным опытом - набором осознанных решений. Я не призываю вас использовать эти решения. Но они могут оказаться хорошим выбором, если вы не знаете с чего начать. С получением личного опыта инструментарий будет доработан под ваши нужды.
|
217
|
+
|
218
|
+
Основные принципы, которыми мы руководствовались:
|
219
|
+
|
220
|
+
- Простота решения. Делать сложные вещи простыми, а не простые сложными.
|
221
|
+
- Прагматизм. Всегда нужно смотреть по ситуации, и при отсутствии имеющегося решения вырабатывать новое. Старайтесь все типизировать, но избегайте догматизма.
|
222
|
+
- Код != документация. Код это инструкции для машины, документация это инструкция для людей. Не нужно их путать и выдавать одно за другое.
|
223
|
+
- [SOLID](https://subvisual.co/blog/posts/19-solid-principles-in-ruby/)
|
224
|
+
|
225
|
+
## Реализация доменов
|
226
|
+
|
227
|
+
Очень удобно выделять домены как отдельные микросервисы.
|
228
|
+
|
229
|
+
> Микросервисы - это отдельное приложение, реализующее логику одного домена.
|
230
|
+
|
231
|
+
В DDD-разработке принципом выделения микросервиса в отдельное приложение будет служить ограниченный контекст. Это не отменяет технический принцип разделения сервисов (если это обусловлено необходимостью обеспечения высокой производительности). Но контекстный принцип будет доминирующим и обязательным.
|
232
|
+
|
233
|
+
## Связи между доменами
|
234
|
+
|
235
|
+
Связи между доменами это всегда API. Это может быть RESTful json api, gRPC, AMPQ. Мы не будем в рамках данной статьи сравнивать один протокол с другим и выделять их преимущества и недостатки, у каждого из них есть своя область применения. Но все же, остановимся на общих рекомендациях:
|
236
|
+
|
237
|
+
__Будте гибкими в выборе протокола и жесткими в однотипности его реализации.__
|
238
|
+
|
239
|
+
|
240
|
+
Выбирайте протокол для каждой пары доменов индивидуально, не старайтесь везде использовать http, возможно, вам где-то понадобится асинхронные очереди и преимущества AMPQ для вас станут очевидными. Не игнорируйте эту возможность потому, что у вас везде RESTful.
|
241
|
+
|
242
|
+
С другой стороны, если вы реализуйте RESTful json, используйте один стандарт структуризации данных. Можете взять готовый например jsonapi или openapi. Если по каким-то причинам готовые решения вас не устраивают и вы чувствуете, что можете разработать свой стандарт - опишите и используйте его. Но применяйте его везде, не разводите "зоопарк" стандартов. Если вам нужно общаться с внешней системой, где про ваши стандарты ничего знают, напишите микросервис адаптер.
|
243
|
+
|
244
|
+
![Adapter](_imgs/adapter.png)
|
245
|
+
|
246
|
+
|
247
|
+
## Реализация сабдоменов
|
248
|
+
|
249
|
+
Как отдельные модули внутри микросервиса.
|
250
|
+
|
251
|
+
> Модуль - это реализация сабдомена, путем вынесения логики в отдельное пространство имен (Namespace) в рамках одного микросервиса.
|
252
|
+
|
253
|
+
Как это все выглядит? Давайте рассмотрим на примере. Как мы помним, у нас есть домен __Работа с курьерами__ (Delivery system) - у этого домена есть три сабдомена:
|
254
|
+
- регистрация курьера (registration)
|
255
|
+
- выдача заданий (tasks)
|
256
|
+
- регистрация заявок на вывод заработанных курьером средств (withdrawal)
|
257
|
+
- проверка того, что ваш микросервис работает, вспомогательное, техническое средство(healt_checker)
|
258
|
+
|
259
|
+
Представим это все в виде структуры папок:
|
260
|
+
```bash
|
261
|
+
$ tree --dirsfirst delivery_system
|
262
|
+
|
263
|
+
delivery_system
|
264
|
+
├── app/
|
265
|
+
│ ├── health_checker/
|
266
|
+
│ │ └── endpoints.rb
|
267
|
+
│ ├── registrations/
|
268
|
+
│ │ ├── entities/
|
269
|
+
│ │ ├── forms/
|
270
|
+
│ │ ├── repositories/
|
271
|
+
│ │ ├── interactor/
|
272
|
+
│ │ ├── services/
|
273
|
+
│ │ ├── validations/
|
274
|
+
│ │ ├── endpoints.rb
|
275
|
+
│ │ └── helpers.rb
|
276
|
+
│ ├── tasks
|
277
|
+
│ │ ├── entities/
|
278
|
+
│ │ ├── queries/
|
279
|
+
│ │ ├── repositories/
|
280
|
+
│ │ ├── endpoints.rb
|
281
|
+
│ │ └── helpers.rb
|
282
|
+
│ └── withdrawals
|
283
|
+
│ ├── entities/
|
284
|
+
│ ├── forms/
|
285
|
+
│ ├── repositories/
|
286
|
+
│ ├── interactor/
|
287
|
+
│ ├── services/
|
288
|
+
│ ├── validations/
|
289
|
+
│ ├── endpoints.rb
|
290
|
+
│ └── helpers.rb
|
291
|
+
├── config/
|
292
|
+
├── db/
|
293
|
+
├── docs/
|
294
|
+
├── lib/
|
295
|
+
│ ├── schemas/
|
296
|
+
│ └── values/
|
297
|
+
├── public
|
298
|
+
├── specs
|
299
|
+
├── config.ru
|
300
|
+
├── Gemfile
|
301
|
+
├── Gemfile.lock
|
302
|
+
├── Rakefile
|
303
|
+
└── README.md
|
304
|
+
```
|
305
|
+
|
306
|
+
Каждая папка в директории `apps/` реализует тот или иной сабдомен, внутри каждого домена есть различные паттерны: `entities`, `forms`, `services` и др. Мы рассмотрим каждый из применяемых паттернов подробно в одной из будущих статей.
|
307
|
+
|
308
|
+
Каждый такой паттерн реализован в соответствующем пространстве имен (Namespace). Например форма создания завяки на выплату курьеру:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
module Withdrawal # Имя сабдомена
|
312
|
+
module Forms #Реализуемый паттерн
|
313
|
+
class Create
|
314
|
+
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
```
|
319
|
+
|
320
|
+
## Связи между сабдоменами
|
321
|
+
|
322
|
+
Давайте рассмотрим конкретный пример. У нас есть учетная запись курьера: `Registrations::Entities::Account`. Она относится к сабдомену `Registrations` - так как мы рассматриваем данный домен не как процесс регистрации, а, скорее, как учетный стол и регистрационную книгу, о чем указанно в нашей документации для бизнеса.
|
323
|
+
|
324
|
+
У нас есть два _Процесса_ при исполнении которых мы обращаемся к этой учетной записи.
|
325
|
+
|
326
|
+
- Создание учетной записи (Registration)
|
327
|
+
- Создание заявки на вывод заработанных курьером средств (Wihtdrawal)
|
328
|
+
|
329
|
+
Как мы видим эти два процесса относятся к разным сабдоменам - Registration и Wihtdrawal.
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
module Registrations
|
333
|
+
module Serivices
|
334
|
+
class CreateAccount
|
335
|
+
def call
|
336
|
+
account = Entities::Account.new
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
module Withdrwals
|
343
|
+
module Serivices
|
344
|
+
class CreateOrder
|
345
|
+
def call
|
346
|
+
account = Registrations::Entities::Account.new
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
353
|
+
В первом обращение ообращение к классу будет реализованно через вызов `Entities::Account`. А во втором случае через явный вызов `Registrations::Entities::Account`. Т.е. если мы явно указываем сабдомен, значит класс из другого сабдомена и так мы четко обозначаем связь.
|
354
|
+
|
355
|
+
Если класс не относится явно ни к одному из сабдоменов, есть смысл выносить его в папку `lib/`. Как правило, это классы, реализующие паттерн 'ValueObject'. Мы рассмотри этот паттерн подробнее в одной из следующих статей.
|
356
|
+
|
357
|
+
|
358
|
+
# Реализация через модель
|
359
|
+
|
360
|
+
|
361
|
+
Процитирую Эрика Эванса:
|
362
|
+
|
363
|
+
> Если архитектура программы или хотя бы некая ее центральная часть, не соответствует структуре модели предметной области, то такая модель практически бесполезна, и правильность работы программы тоже нужно поставить под подозрение. В то же время слишком сложные взаимосвязи между моделями и функциями в программной архитектуре трудно поддаются пониманию, и на практике их сложно сохранить по мере изменения архитектуры.
|
364
|
+
|
365
|
+
Давайте вспомним пример хорошей модели, который я уже приводил в начале этой статьи - топографическая карта. У нас стоит цель - иметь возможность быстро найти расстояние между двумя населенными пунктами. Мы могли бы воспользоваться справочником-таблицей с указанием двух точек между городами. А можем воспользоваться картой. И там и там мы получим один и тот же результат примерно за одно и то же время. Но карта компактнее, она точнее отображает предметную область, она универсальнее. Карта как модель невероятно выразительна. И если рассматривать ее в рамках данной задачи, то померить расстояние удобнее по карте, чем на самой территории, которую она отражает. Модель, которая отражает предметную область, может превосходить ее в некоторых свойствах. Это действительно потрясает воображение.
|
366
|
+
|
367
|
+
Реализация модели это всегда творческий процесс с непредсказуемым результатом. Качество вашего кода это не его производительность, и не его сложность, это простота и выразительность. Совершенствуйте его через постоянный рефакторинг, делайте его гибким и отсекайте все лишнее. Разделите слой, который будет отвечать за бизнес логику модели, от слоев, чья необходимость обусловлена технической реализацией. О том, как нам удалось это сделать, будет рассказано в дальнейшем.
|
368
|
+
|
369
|
+
---
|
370
|
+
- Проблемно-ориентированное проектирование, Эрик Дж. Эванс
|
371
|
+
- Создание микросервисов, Сэм Ньюман
|
372
|
+
- http://gorodinski.com/blog/2013/04/29/sub-domains-and-bounded-contexts-in-domain-driven-design-ddd/
|
373
|
+
- https://martinfowler.com/bliki/BoundedContext.html
|
374
|
+
- https://habr.com/post/316438/
|
375
|
+
- https://dotnetcodr.com/2015/08/06/domain-driven-design-with-web-api-revisited-part-1-introduction/
|
376
|
+
- История абдеритов, Кристов Мартин Виланд
|