Git Product home page Git Product logo

system-design's Introduction

DDD

Заметки о Domain driver design

Domain driven design (предметно-ориентированное проектирование) - набор практик разработки ПО, фокусирующийся на понятиях предметной области (домена). Может включать в себя поддомены.

Ubiquitous language (вездесущий язык) - некий словарь терминов нашего домена. Формируется в результате обсуждения бизнес-области с аналитиками и доменными экспертами.

Bounded context (ограниченный контекст) - подобласть конкректного домена, где используется один ubiquitous language. Например, в компании может быть несколько отделов: отдел зарплат, отдел кадров и т.д. И каждому отделу интересны только отдельные характеристики сотрудников в этих отделах. Но при этом в каждом отделе используется одно понятие - "сотрудник".

Отличие поддоментов и bounded context: в идеале каждый поддомент = один bounded context. Однако может быть что большой поддомен включает в себя несколько bounded context. Либо один bounded context включает в себя несколько поддоменов.

Entities (сущности) - модели, обитатели конкректного bounded context. Можно проверять как наличие ее в UI пользователя. У каждой сущности должен быть уникальный идентификатор - ID. Может создаваться на стороне сервисов (application generated ID - java.util.UUID), базы (sequence), может приходить из другого bounded context (скорее всего это будет отдельный микросервис).

Раняя и поздняя генерация ID: sequence-формирование ID происходит в 2 запроса к базе - при первом мы его получаем, при втором выполняем сохранение. Однако есть вариант и с автоикрементов колонок - ID приходит после сохранения. Это важно в случае event-driven систем, когда важен порядок прихода событий.

Value object (объекты-значения) - описывает entities, количественное его измерение. Аналогия ссылочного типа данных в Java у объекта.

Aggregated (агрегаты) - представляет из себя кластер сущностей, логически связанных друг с другом. Изменение сущностей из агрегата мы делегируем самому агрегату. Создано преимущественно для одновременного обновления и атомарности. Пример из интернет-магазина: корзина и товар. Можем смоделировать так, что это два разных агрегата, две разных таблицы в бд. У каждого товара в этом случае ссылка на корзину. Вдруг понадобилось иметь счетчик в каждой корзине. Тогда если 3 товара в корзине = счетчик корзины равен 3. В этом случае обновление базы требует двух отдельных операций. По какой-то причине запрос может упасть. Получим неконсистентность данных.

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

Изоляция транзакций (transactional isolation) - методы разрешения одновременного выполнения транзакций. Их есть 4 уровня: read uncommited, read commited, repeatable read, serialization. Есть базы, в которых нет уровня изоляций (Mongo DB). В этом случае можно применить isolation with optimistic locking. В этом случае можно каждому агрегату поставить ID, при считвании увеличивать на один выполнять изменение, и далее снова считывать версию, и если ранее считанный ID + 1 равен текущему + 1, то сохранять изменения.

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

Strict and eventual consistency (строгая и конечная согласованность) - разные виды консистенции. Агрегат - строгая консистенция. Если операция выполнена, то все сущности находятся в консистентности мгновенно. Конечная же согласованность - согласованность данных будет достигнута, но в конечном итоге. Т.е. может существовать временной промежуток, в котором сущности могут быть в unconsistency состоянии. Пример: служба доставки. Два разных контекста - сервис доставки и сервис заказов товара, работающих с разными БД. Понятно, что в рамках одной транзакции мы не сможем обновить и статус доставки, и статус заказа, т.к. как минимум разные базы.

Размер кластера агрегата можно выбирать 2 путями: от одного большого, либо постепенным объединением сущностей. Нам выгодно чтобы агрегаты были как можно меньшего размера => плюс производительности. Однако же бизнес может требовать одновременного обновления, требуя строгую консистентность.

EDA

Заметки о Event-driven architecture

Event-driven architecture - подход к проектированию системы, основанный на событиях, или ивентах. Систему предлагается строить как набор компонентов, производящих и потребляющих события. Eda - асинхронной подход к проектированию.

Event (событие) - структура данных, которая содержит информацию о том, что в системе произошло изменение. Event - сообщение о происхождении события. Сообщение как бы несет в себе событие. При этом у сообщения есть адресат, а у события нет.

Особенности и преимущества EDA:

  • Смена направленности коммуникаций. В синхронных сервисах сервис знает что дергать дальше по бизнес цепочке. В асинхронных системах же сервис лишь испускает событие, не зная, что с ним случится дальше. Здесь API сервиса - набор событий, который они генерируют.

  • Надежность системы - нам без разницы, доступен ли сервис, который должен обрабатывать event.

  • Разделение обязанностей CQRS - command and query responsibility segregation. Здесь запросы на чтение и запись разделяются. Можно в зависимости от преобладающих операций масштабировать или сервисы на чтение, или на запись. Также можно поддерживать собственные базы данных.

Query - запросы на чтение данных к агрегатам (сервисам).

Command - запросы на изменение данных в агрегатах (создание, изменение, удаление). Это функция, которая принимает состояние агрегата и проверяет, можно ли провести обновление. Порождают события для eventual consistency всей системы. Команда может порождать один или несколько ивентов.

Важно обеспечить атомарность двух операций команды - изменение данных в бд и отправкой ивента. Также может нарушиться порядок ивентов, которые летят в брокер сообщений. Т.е. в базе статус сначала меняется на in-progress, review, но события уходят в обратном порядке. Таким образом, у подписчиков может сложиться другая картина произошедших действий. На помощь приходит transactional outbox pattern.

Transactional outbox pattern - в рамках одной транзакции в бд мы и обновляем агрегат, и сохраняем набор ивентов для этого обновления. Помимо этого нужен будет еще background-процесс, который будет смотреть на таблицу с ивентами и заниматься их отправлением в БД. Он должен отправлять эти ивенты в нужном порядке в message-broker и отметить их отправнными. Порядок считывания нужно также гарантировать консюмерами, на стороне других агрегатов (осторожно с параллельностью на сервисе-консюмере). Также message-broker надо выбрать, который поддерживает порядок сообщений.

Мы сказали, что в рамках одной транзакции и обновляем таблицу с агрегатом, и таблицу с ивентами. Однако не все БД поддерживают мультитабличную атомарность (ACID-транзакции). Тогда нужно надеяться что в базе есть атомарное обновление одной сущности. Атомарное обновление сущности - это когда есть документ, или рекорд, который мы набором действий можем изменить. Тогда мы включаем список изменений прямо в документ помимо состояние агрегата. Следующий шаг - event sourcing, или хранение только ивентов агрегата без его состояния.

ES

Заметки о Event-sourcing

Event-sourcing - паттерн, когда мы храним не последнее состояние агрегата после череды обновлений, а храним путь из событий, позволяющий воссоздать состояние объекта. Event-log как раз хранит события.

Премущества:

  • Хранение изменений вместо хранения состояний
  • Проще отслеживать порядок действий над агрегатами - проще поддерживать пользователей
  • Отслеживание ошибок за счет видения полной цепочки действий
  • Храня все события, мы можем делиться с другими микросервисами. Если другой микрос встраивается, просто импортируем всю цепочку действий Аналитика, метрика

Минусы:

  • Не везде стоит его применять
  • Сложность реализации - не так распространено, специфические скилы команды
  • Не все usecases покрываются имеющимися фрейморками, часто компании разрабатывают свои инструменты
  • Вместе с event-sourcing часто будет использоваться CQRS, ибо event-sourcing не покрывает оптимально чтение

Как через запрос (например по aggregateID) получить свежее состояние агрегата?

  1. Получаем через запрос все List ивенты по aggregateID
  2. Создаем агрегат с пустым состоянием
  3. Последовательно применяем к агрегат набор ивентов: Empty state => State version 1 => ... => State version N и получим актуальное состояние агрегата

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

Snapshot-оптимизация: чтобы обновить агрегат, мы должны получить все его ивенты. Их может быть очень много. Отсюда излишние затраты к ресурсам, еще хуже с многопоточным доступом. Вместо того, чтобы просто сохранять события, порожденные командой, в event-store, мы выполним transit-функцию к стриму событий, получим последнее состояние агрегата и сохраним в snapshot-базу с версией - количеством ивентов. Тогда при следующем запросе на обновление агрегата мы полезем не в event-store за ивентами, а в snap-store за последним состоянием объекта и количеством ивентов, которые мы уже успели применить. Таким образом, мы полезем в event-store только за теми событиями, которые произошло после последнего снэпшота.

Дополнительно

2PC commit

2PC commit (2 phase commit) - алгоритм консенсуса, двухфазный коммит, позволяет выполнить транзакцию на двух разных машинах. Выделяем транзакцию как агрегат, хотим перевести 100 рублей с одного аккаунта на другой. Тогда первая фаза - проверка инвариантов, подтверждение что готовы к переводу, запись этого в стэйт агрегата аккаунта. Транзакция собирает ответы, если все говорят окей - отправляем событие, коммитим, что переводим. На 1 этапе можно реализовать 2 разными способами:

  • redo log - храним изменения в транзакции, которые нужно внести, не меняет стэйт агрегата аккаунта
  • undo log - сразу меняем стэйт аккаунта, тоже храним изменение в транзакции После отката на 2 фазе могут нарушаться инварианты, требует обсуждения с аналитиками

Линеаризация и последовательная спецификация

Пусть в контексте многопоточности есть общий ресурс, например, очередь. Есть 2 потока, производящие действия над ней (запись, чтение). Действие != моментально, у него есть момент начала, процесс и конец действия. Действия, для которых время конца одного < время начала другого соединены отношением happens before. Мы можем выстроить последовательность действий, соединенных порядок happens before. Но некоторые действия могут выполняться параллельно, тогда эти действия мы можем попытаться соединить с другими через happens before. Если при этом не нарушется последовательная спецификация (нет дальнейших нарушений) - выстроили верно. Весь этот процесс - процесс линеаризации. Линеаризция == атомарность в контексте многопоточности (классы заявляют свои операции атомарными), а также согласованность.

CAP-теорема

CAP-теорема - применяется для распределенных систем. Consistency, availability, partition tolerance (Консистентные, доступные, устойчивые к отказу сети). Говорит, что нельзя построить систему, удовлетворяющую всем 3 свойствам, только 2. Подробнее:

  • Consistency - свойство линеаризуемости, или согласованность.
  • Availability - любой узел должен иметь возможность ответить на запрос.
  • Partition tolerance - способность системы переживать отказ сети. Позволяет обслуживать запрос пользователя даже если потерялась связь между узлами.

system-design's People

Contributors

temimo avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.