Паттерн Strategy
Я хочу признаться: я не люблю if'ы, да и switch'и тоже :)
Часто бывает, что за if'ами скрывается разделение функциональности по какому-то типу. Например, отправка SMS и отправка сообщения в какую-то CRM. Или метод коммуникации: через SMS или посредством звонка.
А сам выбор типа коммуникации зависит от каких-то настроек (приложения или конкретного пользователя).
То есть, возможны две стратегии коммуникации: SMS или CRM. Так вот, паттерн стратегия предлагает изолировать конкретные стратегии в отдельные классы (структуры), а выше них ставить класс, который будет ими управлять.
Как мы уже знаем, проектирование всегда идет от модели данных. В нашем случае это набор данных, по которым мы можем понять, кому, как и что нужно отправить.
Далее определяем интерфейс, который будет реализован стратегиями:
Реализуем этот интерфейс:
Ну и сам менеджер стратегий:
Таким образом, менеджер содержит набор всех возможных стратегий коммуникации. Их не обязательно задавать в фабричном методе - можно и передавать. Используя данные из модели, определяем стратегию и отправляем сообщение.
Взглянем немного шире: таким образом, мы поддержали Open-Close Principle.
Конечно сам выбор стратегии может быть сложнее - тогда мы просто изолируем выбор стратегии в отдельный метод менеджера.
Дальше будет еще несколько постов про самые важные, на мой взгляд, паттерны.
#patterns
Я хочу признаться: я не люблю if'ы, да и switch'и тоже :)
Часто бывает, что за if'ами скрывается разделение функциональности по какому-то типу. Например, отправка SMS и отправка сообщения в какую-то CRM. Или метод коммуникации: через SMS или посредством звонка.
А сам выбор типа коммуникации зависит от каких-то настроек (приложения или конкретного пользователя).
То есть, возможны две стратегии коммуникации: SMS или CRM. Так вот, паттерн стратегия предлагает изолировать конкретные стратегии в отдельные классы (структуры), а выше них ставить класс, который будет ими управлять.
Как мы уже знаем, проектирование всегда идет от модели данных. В нашем случае это набор данных, по которым мы можем понять, кому, как и что нужно отправить.
type CommunicationStrategy string
const (
SMS CommunicationStrategy = "SMS"
AmoCRM CommunicationStrategy = "AmoCRM"
)
type Message struct {
From string
To string
Text string
CommunicateVIA CommunicationStrategy
}
Далее определяем интерфейс, который будет реализован стратегиями:
type Communitator interface {
SendMessage(context.Context, Message) error
}Реализуем этот интерфейс:
type SMS struct {
}
func (c *SMS) SendMessage(ctx context.Context, msg Message) error {
// подключение к SMS-сервису
// отправка SMS
return nil
}
type AmoCRM struct {
}
func (c *AmoCRM) SendMessage(ctx context.Context, msg Message) error {
// работа с API
return nil
}Ну и сам менеджер стратегий:
type CommunicationManager struct {
strategies map[CommunicationStrategy]Communitator
}
func NewCommunicationManager() *CommunicationManager {
return &CommunicationManager{
strategies: map[CommunicationStrategy]Communitator{
SMS: &SMS{},
AmoCRM: &AmoCRM{},
},
}
}
func (m *CommunicationManager) SendMessage(ctx context.Context, msg Message) error {
if communitator, ok := m.strategies[msg.CommunicateVIA]; ok {
return communitator.SendMessage(ctx, msg)
}
return fmt.Errorf("unsupported communication strategy: %s", msg.CommunicateVIA)
}Таким образом, менеджер содержит набор всех возможных стратегий коммуникации. Их не обязательно задавать в фабричном методе - можно и передавать. Используя данные из модели, определяем стратегию и отправляем сообщение.
Взглянем немного шире: таким образом, мы поддержали Open-Close Principle.
Конечно сам выбор стратегии может быть сложнее - тогда мы просто изолируем выбор стратегии в отдельный метод менеджера.
Дальше будет еще несколько постов про самые важные, на мой взгляд, паттерны.
#patterns
👏5👍2🫡2
Паттерн Specification
Этот паттерн помогает выразить сложные бизнес-правила (критерии) через отдельные, переиспользуемые объекты.
Пример правил:
- Пользователь должен быть старше 18 лет.
- Пользователь должен быть активным.
- Пользователь должен иметь роль покупателя.
Решение в лоб: наделать кучу ифов.
Решение по уму: паттерн Specification, где
- Выносим каждое правило в отдельный объект.
- Легко тестировать.
- Легко комбинировать друг с другом.
Для начала определим интерфейс проверки правил:
Добавим пару спецификаций:
И их комбинацию:
OR-спецификация выглядит так:
Проверяем работу:
Когда использовать Specification:
- Выражение сложных бизнес-правил в коде.
- Когда бизнес-правила часто меняются.
- Когда нужно переиспользовать правила в разный частях приложения.
#patterns
Этот паттерн помогает выразить сложные бизнес-правила (критерии) через отдельные, переиспользуемые объекты.
Пример правил:
- Пользователь должен быть старше 18 лет.
- Пользователь должен быть активным.
- Пользователь должен иметь роль покупателя.
type User struct {
Age int
IsActive bool
}Решение в лоб: наделать кучу ифов.
Решение по уму: паттерн Specification, где
- Выносим каждое правило в отдельный объект.
- Легко тестировать.
- Легко комбинировать друг с другом.
Для начала определим интерфейс проверки правил:
type Specification[T any] interface {
IsValid(candidate T) bool
}Добавим пару спецификаций:
type AgeSpecification struct {
minAge int
}
func NewAgeSpecification(minAge int) *AgeSpecification {
return &AgeSpecification{minAge: minAge}
}
func (s *AgeSpecification) IsValid(candidate User) bool {
return candidate.Age >= s.minAge
}
type ActiveSpecification struct{}
func NewActiveSpecification() *ActiveSpecification {
return &ActiveSpecification{}
}
func (s *ActiveSpecification) IsValid(candidate User) bool {
return candidate.IsActive
}И их комбинацию:
type AndSpecification[T any] struct {
specifications []Specification[T]
}
func NewAndSpecification[T any](specifications ...Specification[T]) *AndSpecification[T] {
return &AndSpecification[T]{specifications: specifications}
}
func (s *AndSpecification[T]) IsValid(candidate T) bool {
for _, specification := range s.specifications {
if !specification.IsValid(candidate) {
return false
}
}
return true
}OR-спецификация выглядит так:
func (s *OrSpecification) IsValid(candidate T) bool {
for _, specification := range s.specifications {
if specification.IsValid(product) {
return true
}
}
return false
}Проверяем работу:
func main() {
user := User{Age: 20, IsActive: false}
andSpec := NewAndSpecification(
NewAgeSpecification(18),
NewActiveSpecification(),
)
fmt.Println(andSpec.IsValid(user))
}Когда использовать Specification:
- Выражение сложных бизнес-правил в коде.
- Когда бизнес-правила часто меняются.
- Когда нужно переиспользовать правила в разный частях приложения.
#patterns
👍4🫡2
Паттерн Outbox
Паттерн обеспечивает атомарность при работе с БД и брокерами сообщений.
Пример 1: Идет транзакция изменения агрегата. И на каком-то шаге нужно отправить ивент в Kafka, отправляем ивент, а на следующем шаге транзакция падает с ошибкой. Но ивент уже отправлен.
Пример 2: Консюмер читает ивенты из Kafka и выполняет действия. Вопрос: как обеспечить идемпотентность?
Основная идея:
🔸Сохраняем событие в таблицу outbox в рамках той же транзакции, что и изменение данных агрегата.
🔸Фоновым процессом читаем эту таблицу и отправляем события в брокер.
🔸После успешной отправки или меняем статус события, или удаляем из таблицы.
Пример таблицы:
Далее добавляем сущность OrderEvent и репозиторий ее сохранения.
Фоновый процесс:
В простом случае - удаляем, но можем и менять статус. А также можно считать количество попыток, добавить метрики, подумать про лочки.
Как обработать событие со стороны консюмера?
Точно также: сначала кладем в БД, потом фоновым процессом вычитываем и обрабатываем.
Обратите внимание на EventID, это наша идемпотентность.
Итого
Паттерн отлично подходит для агрегатов и доменных событий. Хорошо ложится в CQRS и Unit of Work.
#patterns #DDD
Паттерн обеспечивает атомарность при работе с БД и брокерами сообщений.
Пример 1: Идет транзакция изменения агрегата. И на каком-то шаге нужно отправить ивент в Kafka, отправляем ивент, а на следующем шаге транзакция падает с ошибкой. Но ивент уже отправлен.
Пример 2: Консюмер читает ивенты из Kafka и выполняет действия. Вопрос: как обеспечить идемпотентность?
Основная идея:
🔸Сохраняем событие в таблицу outbox в рамках той же транзакции, что и изменение данных агрегата.
🔸Фоновым процессом читаем эту таблицу и отправляем события в брокер.
🔸После успешной отправки или меняем статус события, или удаляем из таблицы.
Пример таблицы:
CREATE TABLE outbox (
id UUID PRIMARY KEY,
event_id VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
status VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
Далее добавляем сущность OrderEvent и репозиторий ее сохранения.
Фоновый процесс:
func (w *OutboxWorker) Start(ctx context.Context) {
ticker := time.NewTicker(w.pollInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
events, _ := w.outboxRepo.FetchPending(ctx, 100)
for _, event := range events {
if err := w.producer.Send(event); err != nil {
continue
}
_ = w.outboxRepo.Delete(ctx, event.ID)
}
case <-ctx.Done():
return
}
}
}В простом случае - удаляем, но можем и менять статус. А также можно считать количество попыток, добавить метрики, подумать про лочки.
Как обработать событие со стороны консюмера?
Точно также: сначала кладем в БД, потом фоновым процессом вычитываем и обрабатываем.
Обратите внимание на EventID, это наша идемпотентность.
Итого
Паттерн отлично подходит для агрегатов и доменных событий. Хорошо ложится в CQRS и Unit of Work.
#patterns #DDD
👍5👏3🫡3
Паттерн Saga
Мы уже говорили о паттерне Unit of Work, когда у нас есть действия, которые надо выполнить атомарно в рамках одной БД.
Но, что если у нас микросервисы и нам нужно сделать последовательно несколько изменений в разных агрегатах, да еще и в разных ограниченных контекстах (читай микросервисах)?
Суть паттерна:
🔸Транзакции выполняются локально в каждом сервисе.
🔸По окончании транзакции генерируется событие для следующего сервиса.
🔸Если какая-либо транзакция заканчивается ошибкой, нужно последовательно выполнить компенсирующие действия, чтобы отменить все изменения.
Типы Saga:
🔸Оркестрация
Вся власть сосредоточена в одних руках: центральный оркестратор управляет выполнением шагов. А также умеет вызывать компенсирующие действия.
То есть по сути, оркестратор читает события и вызывает команды микросервисов.
Этот тип саги хорош тем, что в нем есть четкий контроль потока и легкая отладка, но при это имеется центральная точка отказа.
🔸Хореография
В этом случае ответственность за распределенную транзакцию ложится на каждый микросервис, участвующий в ней.
Суть та же самая: первый микросервис выполняет часть своей транзакции и отправляем ивент. Второй микросервис читает этот ивент и выполняет следующий шаг. Если возникает ошибка: микросервис выбрасывает ивент, предыдущий выполняет компенсацию и также выбрасывает ивент и т.д.
Этот тип саги хорош тем, что в нем есть высокая масштабируемость и слабая связность, но сложная отладка и отслеживание.
Связь с DDD
🔸При изменении агрегата генерируется событие, которое и запускает шаги саги.
🔸Unit of Work выполняет отдельный шаг саги. В этом ему помогают паттерны Repository и Outbox.
Сложности
🔸Отсутствие идемпотентности. Как это исправлять читаем предыдущий пост Outbox.
🔸Бесконечная компенсация. А что будет если компенсирующие действие также завершится ошибкой? Смотрим в сторону кол-ва попыток и алертов.
#patterns #DDD
Мы уже говорили о паттерне Unit of Work, когда у нас есть действия, которые надо выполнить атомарно в рамках одной БД.
Но, что если у нас микросервисы и нам нужно сделать последовательно несколько изменений в разных агрегатах, да еще и в разных ограниченных контекстах (читай микросервисах)?
Суть паттерна:
🔸Транзакции выполняются локально в каждом сервисе.
🔸По окончании транзакции генерируется событие для следующего сервиса.
🔸Если какая-либо транзакция заканчивается ошибкой, нужно последовательно выполнить компенсирующие действия, чтобы отменить все изменения.
Типы Saga:
🔸Оркестрация
Вся власть сосредоточена в одних руках: центральный оркестратор управляет выполнением шагов. А также умеет вызывать компенсирующие действия.
То есть по сути, оркестратор читает события и вызывает команды микросервисов.
Этот тип саги хорош тем, что в нем есть четкий контроль потока и легкая отладка, но при это имеется центральная точка отказа.
🔸Хореография
В этом случае ответственность за распределенную транзакцию ложится на каждый микросервис, участвующий в ней.
Суть та же самая: первый микросервис выполняет часть своей транзакции и отправляем ивент. Второй микросервис читает этот ивент и выполняет следующий шаг. Если возникает ошибка: микросервис выбрасывает ивент, предыдущий выполняет компенсацию и также выбрасывает ивент и т.д.
Этот тип саги хорош тем, что в нем есть высокая масштабируемость и слабая связность, но сложная отладка и отслеживание.
Связь с DDD
🔸При изменении агрегата генерируется событие, которое и запускает шаги саги.
🔸Unit of Work выполняет отдельный шаг саги. В этом ему помогают паттерны Repository и Outbox.
Сложности
🔸Отсутствие идемпотентности. Как это исправлять читаем предыдущий пост Outbox.
🔸Бесконечная компенсация. А что будет если компенсирующие действие также завершится ошибкой? Смотрим в сторону кол-ва попыток и алертов.
#patterns #DDD
Telegram
От сердца к коду
Паттерн Unit of Work
Это паттерн проектирования, который группирует несколько связанных операций с данными в одну атомарную транзакцию. Все изменения будут или сохранены или отменены как единое целое.
Задача: нужно атомарно изменить данные двух агрегатов.…
Это паттерн проектирования, который группирует несколько связанных операций с данными в одну атомарную транзакцию. Все изменения будут или сохранены или отменены как единое целое.
Задача: нужно атомарно изменить данные двух агрегатов.…
🔥5🫡2
Месяц ничего не выкладывал, сначала был отпуск, потом лень. Каюсь.
Пришло время вернуться к регулярным постам 2-3 раза в неделю. Также будем продолжать разбирать архитектуру и паттерны, но хочу подмешать еще и менеджерские вопросы. Сам я уже какое-то время почти не пишу код, лет 6 работаю техлидом, а сейчас руководителем разработки, кажется есть что сказать по этому поводу.
Постараюсь не нудеть и не отходить далеко от проблем кода. Продолжаем...
Пришло время вернуться к регулярным постам 2-3 раза в неделю. Также будем продолжать разбирать архитектуру и паттерны, но хочу подмешать еще и менеджерские вопросы. Сам я уже какое-то время почти не пишу код, лет 6 работаю техлидом, а сейчас руководителем разработки, кажется есть что сказать по этому поводу.
Постараюсь не нудеть и не отходить далеко от проблем кода. Продолжаем...
❤13🫡3
Роль техлида в команде разработки
Да, честно сказать в разных компаниях эта роль означает разное. Где-то были уже выстроенные процессы, нужно было просто в них влиться и отвечать за качество разработки. Где-то процессов или не было или они были плохими и приходилось заниматься выстраиванием процессов, а потом уже качеством разработки. Т.е. в одном месте перевес может быть в менеджерскую сторону, а в другом в техническую.
В целом лид должен уметь и то и то, но не просто уметь, а иметь опыт и понимать как процессы и качество отражаются на команде. С одной стороны хорошие технические навыки: понятие качественного кода, архитектуры приложений. А с другой стороны: высокие софт скиллы, скиллы по фасилитации, стратегически понимать куда идет команда и как ее туда довести.
Недавно, в одной из книг прочел, что лид служит своей команде, еще раз: лид работает для команды, а не наоборот.
🔸А теперь лично мой чеклист для лида:
- Понимание ритуалов Agile/Scrum
- Работа с непрерывным обучением команды, выращивание себе замены
- Распределение задач с учетом навыком, интересов и нагрузки
- Умение правильно делегировать задачи
- Замер температуры: проведение 1 на 1
- Создание психологически комфортной среды в команде
- Участие в найме
- Работа с метриками
- Коммуникации
Технически:
- Архитектура и дизайн
- Работа с техдолгом и багами
- Код-ревью и стандарты повышения качества
- Финальное слово в спорных технических вопросах
Как будто бы менеджерских навыков надо побольше?) Наверное, так и есть.
🔸А теперь давайте обсудим еще несколько вопросов кратко.
Надо ли писать код? Лично я уверен что надо, скажем так, 20-30% времени. Не забивайте на это дело! Если перестать писать код совсем - можно быстро обесцениться.
Как из хорошего разработчика сделать плохого менеджера?) Да-да, сделать его лидом.
Зачем оно тебе? Ну вот пришел в компанию с плохими процессами, а у тебя есть опыт работы в хороших. Если ты просто разработчик - у тебя нет влияния на процессы, а очень хочется.
Лидство на интуиции. Бывает такое, что у человека высокие софт скиллы сами по себе. Он интуитивно понимает потребности и направления команды. Достаточно ли этого, чтобы стать хорошим лидом? - Нет. Интуицию надо дополнять теорией, а теорию практикой.
🔸Работа лида измеряется работой его команды. Не забываем про метрики:
- снижение числа инцедентов, скорость доставки фич
- кол-во багов в проде
- процент времени, который разработчики именно пишут код
- рост уровня разработки
- выполнение планов релизов
🔸И на последок, как всегда, несколько книг для начала:
- Мама, я тимлид! Практические советы по руководству IT-командой
- Карьера Software Engineering Manager. Эффективное управление командой разработчиков ПО
- Человеческий фактор. Успешные проекты и команды
- Проект "Феникс"
- Идеальный руководитель: Почему им нельзя стать и что из этого следует
По менеджменту в IT, и не только, книг огромное количество, выше я указал лишь те, что больше мне запомнились.
#management #teamlead
Да, честно сказать в разных компаниях эта роль означает разное. Где-то были уже выстроенные процессы, нужно было просто в них влиться и отвечать за качество разработки. Где-то процессов или не было или они были плохими и приходилось заниматься выстраиванием процессов, а потом уже качеством разработки. Т.е. в одном месте перевес может быть в менеджерскую сторону, а в другом в техническую.
В целом лид должен уметь и то и то, но не просто уметь, а иметь опыт и понимать как процессы и качество отражаются на команде. С одной стороны хорошие технические навыки: понятие качественного кода, архитектуры приложений. А с другой стороны: высокие софт скиллы, скиллы по фасилитации, стратегически понимать куда идет команда и как ее туда довести.
Недавно, в одной из книг прочел, что лид служит своей команде, еще раз: лид работает для команды, а не наоборот.
🔸А теперь лично мой чеклист для лида:
- Понимание ритуалов Agile/Scrum
- Работа с непрерывным обучением команды, выращивание себе замены
- Распределение задач с учетом навыком, интересов и нагрузки
- Умение правильно делегировать задачи
- Замер температуры: проведение 1 на 1
- Создание психологически комфортной среды в команде
- Участие в найме
- Работа с метриками
- Коммуникации
Технически:
- Архитектура и дизайн
- Работа с техдолгом и багами
- Код-ревью и стандарты повышения качества
- Финальное слово в спорных технических вопросах
Как будто бы менеджерских навыков надо побольше?) Наверное, так и есть.
🔸А теперь давайте обсудим еще несколько вопросов кратко.
Надо ли писать код? Лично я уверен что надо, скажем так, 20-30% времени. Не забивайте на это дело! Если перестать писать код совсем - можно быстро обесцениться.
Как из хорошего разработчика сделать плохого менеджера?) Да-да, сделать его лидом.
Зачем оно тебе? Ну вот пришел в компанию с плохими процессами, а у тебя есть опыт работы в хороших. Если ты просто разработчик - у тебя нет влияния на процессы, а очень хочется.
Лидство на интуиции. Бывает такое, что у человека высокие софт скиллы сами по себе. Он интуитивно понимает потребности и направления команды. Достаточно ли этого, чтобы стать хорошим лидом? - Нет. Интуицию надо дополнять теорией, а теорию практикой.
🔸Работа лида измеряется работой его команды. Не забываем про метрики:
- снижение числа инцедентов, скорость доставки фич
- кол-во багов в проде
- процент времени, который разработчики именно пишут код
- рост уровня разработки
- выполнение планов релизов
🔸И на последок, как всегда, несколько книг для начала:
- Мама, я тимлид! Практические советы по руководству IT-командой
- Карьера Software Engineering Manager. Эффективное управление командой разработчиков ПО
- Человеческий фактор. Успешные проекты и команды
- Проект "Феникс"
- Идеальный руководитель: Почему им нельзя стать и что из этого следует
По менеджменту в IT, и не только, книг огромное количество, выше я указал лишь те, что больше мне запомнились.
#management #teamlead
❤5🫡3👍2🤔2
Паттерн Policy
Продолжаем разбирать важные, на мой взгляд, паттерны. И сегодня поговорим про политику.
Для меня этот паттерн, по смыслу, напоминает стратегию, но немного проще. Он также позволяет инкапсулировать правила принятия решений, при этом не изменяя текущего кода. Например, расчет скидок, выбор тарифа. Стратегия кажется сложнее, там взаимозаменяемы алгоритмы, изменение данных и т.п.
🔸Итого, политики:
- Не должны быть большими и сложными.
- Не должны изменять состояния системы, только возвращать результат.
- Не должны иметь привязку к инфраструктуре.
🔸Перейдем к коду:
Далее используем политики в аппликейшен сервисе:
🔸Strategy vs Policy vs Specification:
- В Strategy ключевая идея: выбор алгоритма или способа выполнения чего-то. Фокус на том, как что-то делается. Например: выбор способа коммуникации, выбор способа сортировки.
- Specification: определение критериев соответствия. Фокус на что отбирать или проверять (критерии). Например, валидация пользователей, фильтрация списка продуктов по цене, категории и т.п.
- Policy: определение правил или ограничений, которым должен соответствовать объект или процесс. Фокус на что разрешено или запрещено. Например: политика безопасности доступа к ресурсам в зависимости от роли пользователя.
#patterns
Продолжаем разбирать важные, на мой взгляд, паттерны. И сегодня поговорим про политику.
Для меня этот паттерн, по смыслу, напоминает стратегию, но немного проще. Он также позволяет инкапсулировать правила принятия решений, при этом не изменяя текущего кода. Например, расчет скидок, выбор тарифа. Стратегия кажется сложнее, там взаимозаменяемы алгоритмы, изменение данных и т.п.
🔸Итого, политики:
- Не должны быть большими и сложными.
- Не должны изменять состояния системы, только возвращать результат.
- Не должны иметь привязку к инфраструктуре.
🔸Перейдем к коду:
// Интерфейс политики
type DiscountPolicy interface {
CalculateDiscount(ctx context.Context, customer Customer, order Order) Discount
}
// Примеры политик
type VIPDiscountPolicy struct{}
func (p VIPDiscountPolicy) CalculateDiscount(ctx context.Context, customer Customer, _ Order) Discount {
if customer.IsVIP {
return 0.2 // 20%
}
return 0
}
type NewYearDiscountPolicy struct{}
func (p NewYearDiscountPolicy) CalculateDiscount(ctx context.Context, _ Customer, _ Order) Discount {
if time.Now().Month() == time.December {
return 0.25 // 25%
}
return 0
}
// Объединение политик
type BestOfferDiscountPolicy struct {
policies []DiscountPolicy
}
func NewBestOfferDiscountPolicy(policies ...DiscountPolicy) *BestOfferDiscountPolicy {
return &BestOfferDiscountPolicy{policies: policies}
}
func (p *BestOfferDiscountPolicy) CalculateDiscount(ctx context.Context, customer Customer, order Order) Discount {
bestDiscount := Discount(0)
for _, policy := range p.policies {
discount := policy.CalculateDiscount(ctx, customer, order)
if discount > bestDiscount {
bestDiscount = discount
}
}
return bestDiscount
}
Далее используем политики в аппликейшен сервисе:
type OrderService struct {
discountPolicy DiscountPolicy
}
func NewOrderService(policy DiscountPolicy) *OrderService {
return &OrderService{discountPolicy: policy}
}
func (s *OrderService) CalculateTotal(ctx context.Context, customer Customer, order Order) float64 {
discount := s.discountPolicy.CalculateDiscount(ctx, customer, order)
total := order.Subtotal * (1 - discount)
return total
}🔸Strategy vs Policy vs Specification:
- В Strategy ключевая идея: выбор алгоритма или способа выполнения чего-то. Фокус на том, как что-то делается. Например: выбор способа коммуникации, выбор способа сортировки.
- Specification: определение критериев соответствия. Фокус на что отбирать или проверять (критерии). Например, валидация пользователей, фильтрация списка продуктов по цене, категории и т.п.
- Policy: определение правил или ограничений, которым должен соответствовать объект или процесс. Фокус на что разрешено или запрещено. Например: политика безопасности доступа к ресурсам в зависимости от роли пользователя.
#patterns
Telegram
От сердца к коду
Паттерн Strategy
Я хочу признаться: я не люблю if'ы, да и switch'и тоже :)
Часто бывает, что за if'ами скрывается разделение функциональности по какому-то типу. Например, отправка SMS и отправка сообщения в какую-то CRM. Или метод коммуникации: через SMS…
Я хочу признаться: я не люблю if'ы, да и switch'и тоже :)
Часто бывает, что за if'ами скрывается разделение функциональности по какому-то типу. Например, отправка SMS и отправка сообщения в какую-то CRM. Или метод коммуникации: через SMS…
❤3🫡2👍1
Теория ограничений (ТОС) - Элияху Голдратт
🔸 Что это такое?
Теория, которая фокусируется на выявлении и устранений узких мест (ботл неков) в системе для повышения ее эффективности.
Если мы говорим про разработку: ТОС должна помогать оптимизировать процесс доставки продукта, сократить время разработки и улучшить прогнозируемость.
🔸 Борьба с горлышками
Пять шагов фокуса:
1. Найти ограничение - определить что тормозит процесс.
2. Эксплуатировать ограничение - работать в сторону эффективного использования ресурсов (например, освободить SRE от рутины).
3. Подчинить всё остальное ограничению - выстроить процессы так, чтобы ограничение не простаивало (например, отдать в разработку автоматизацию рутинных задач для SRE).
4. Расширить ограничение - увеличить мощность узкого места (нанять больше SRE)
5. Вернуться к шагу 1
Показатели эффективности:
- Пропускная способность — скорость доставки ценности клиенту
- Запасы — незавершённые задачи (например, накопление неоттестированного кода)
- Операционные расходы — затраты на поддержание процессов
- Время цикла (Cycle Time) — как быстро задачи проходят весь процесс
- Простой ресурсов — например, разработчики ждут тестирования
Далее приведу пару проблем и их решение.
🔸 Впихнуть невпихуемое
Ситуация: в спринт включают 20 задач, хотя команда стабильно делает 10. Тогда:
Последствия:
- Накопление незавершённой работы
- Падение качества:
- Растёт количество багов
- Упрощаются код-ревью (или они вообще пропускаются)
- Команда перестаёт рефакторить технический долг.
- Снижение пропускной способности (парадокс):
- Контекстное переключение между задачами «съедает» время
- Ошибки из-за спешки требуют переделок
- Выгорание команды
- Потеря прогнозируемости
Что же делать?
- Ограничение — пропускная способность команды (10 задач/спринт). Брать строго 10 задач всегда!
- Буфер: оставить 20% времени на непредвиденные работы
- Убрать многозадачность, фокус на завершении задач.
🔸 Оценка +15%
Ситуация: на каждую задачу всегда добавляется +15% времени в разработке.
Последствия:
- Увеличение времени цикла (Cycle Time) (заполнения выделенного времени)
- Рост незавершённой работы: накопление задач с завышенной оценкой создаёт иллюзию «загруженности», но реальная пропускная способность (throughput) не увеличивается.
- Маскировка ограничений: локальные запасы мешают выявить реальные узкие места
Когда запас допустим?
- Глобальные буферы: выделить 20% времени спринта на непредвиденные работы
- Учёт вариативности через статистику: Использовать исторические данные, чтобы планировать сроки, а не «накидывать» проценты на каждую задачу.
🔸Метод DBR (Drum-Buffer-Rope)
это подход из Теории ограничений, который синхронизирует работу системы с её «узким местом»:
- Drum (Барабан) — ритм работы ограничения (самого медленного этапа в системе).
- Buffer (Буфер) — защитный запас задач перед ограничением, чтобы оно не простаивало.
- Rope (Верёвка) — механизм, который «привязывает» запуск новых задач к скорости ограничения, предотвращая перегрузку
🔸 Что почитать?
- Цель
- Проект феникс
- Джедайские техники
#management #teamlead
🔸 Что это такое?
Теория, которая фокусируется на выявлении и устранений узких мест (ботл неков) в системе для повышения ее эффективности.
Если мы говорим про разработку: ТОС должна помогать оптимизировать процесс доставки продукта, сократить время разработки и улучшить прогнозируемость.
🔸 Борьба с горлышками
Любые изменения, которые не исправляют горлышко - это иллюзия.
Пять шагов фокуса:
1. Найти ограничение - определить что тормозит процесс.
2. Эксплуатировать ограничение - работать в сторону эффективного использования ресурсов (например, освободить SRE от рутины).
3. Подчинить всё остальное ограничению - выстроить процессы так, чтобы ограничение не простаивало (например, отдать в разработку автоматизацию рутинных задач для SRE).
4. Расширить ограничение - увеличить мощность узкого места (нанять больше SRE)
5. Вернуться к шагу 1
Показатели эффективности:
- Пропускная способность — скорость доставки ценности клиенту
- Запасы — незавершённые задачи (например, накопление неоттестированного кода)
- Операционные расходы — затраты на поддержание процессов
- Время цикла (Cycle Time) — как быстро задачи проходят весь процесс
- Простой ресурсов — например, разработчики ждут тестирования
Далее приведу пару проблем и их решение.
🔸 Впихнуть невпихуемое
Ситуация: в спринт включают 20 задач, хотя команда стабильно делает 10. Тогда:
Последствия:
- Накопление незавершённой работы
- Падение качества:
- Растёт количество багов
- Упрощаются код-ревью (или они вообще пропускаются)
- Команда перестаёт рефакторить технический долг.
- Снижение пропускной способности (парадокс):
- Контекстное переключение между задачами «съедает» время
- Ошибки из-за спешки требуют переделок
- Выгорание команды
- Потеря прогнозируемости
Что же делать?
- Ограничение — пропускная способность команды (10 задач/спринт). Брать строго 10 задач всегда!
- Буфер: оставить 20% времени на непредвиденные работы
- Убрать многозадачность, фокус на завершении задач.
🔸 Оценка +15%
Ситуация: на каждую задачу всегда добавляется +15% времени в разработке.
Последствия:
- Увеличение времени цикла (Cycle Time) (заполнения выделенного времени)
- Рост незавершённой работы: накопление задач с завышенной оценкой создаёт иллюзию «загруженности», но реальная пропускная способность (throughput) не увеличивается.
- Маскировка ограничений: локальные запасы мешают выявить реальные узкие места
Когда запас допустим?
- Глобальные буферы: выделить 20% времени спринта на непредвиденные работы
- Учёт вариативности через статистику: Использовать исторические данные, чтобы планировать сроки, а не «накидывать» проценты на каждую задачу.
🔸Метод DBR (Drum-Buffer-Rope)
это подход из Теории ограничений, который синхронизирует работу системы с её «узким местом»:
- Drum (Барабан) — ритм работы ограничения (самого медленного этапа в системе).
- Buffer (Буфер) — защитный запас задач перед ограничением, чтобы оно не простаивало.
- Rope (Верёвка) — механизм, который «привязывает» запуск новых задач к скорости ограничения, предотвращая перегрузку
🔸 Что почитать?
- Цель
- Проект феникс
- Джедайские техники
#management #teamlead
👍2🫡2👌1
Паттерн API Gateway
Это обратный прокси-сервер, который служит единой точкой входа в микросервисной архитектуре.
Проблема: у нас есть несколько микросервисов со своим API (возможно на разных протоколах), чтобы начать их использовать мы должны знать все их адреса, в каждом иметь авторизацию и т.п.
Решение: разрабатываем отдельный сервис, который принимает все входящие API запросы и маршрутизирует их соответствующим микросервисам.
Основные функции, которые может выполнять API gateway:
- Маршрутизация (уже проговорили)
- Агрегация данных: объединения запросов и ответов к и от нескольких микросервисов
- Аутентификация: как пример, проверка jwt-токена
- Кеширование
- Логирование и метрики
- Ограничение RPS
- Трансформация протоколов: REST -> gRPC
Также gateway хорошо подходит для миграции с legacy кода, замена монолита микросервисами с сохранением старого API.
Недостатки этого решения:
- Единая точка отказа: gateway упал, вся система стала недоступной
- Бутылочное горлышка: весь трафик идет через один узел.
Последнее о чем хотелось бы поговорить, это сравнение API Gateway, Open Host Service(OHS) и Anti-Corruption Layer (ACL):
- API Gateway: когда нужно упростить доступ к нескольким микросервисам. Например, мобильное приложение.
- OHS: когда ваш контекст должны использовать соседние команды/сервисы. Например, сервис платежей предоставляет API для заказов.
- ACL: при интеграции с legacy-системой или внешним API, для защиты доменной модели.
Пример взаимодействия:
#patterns #DDD
Это обратный прокси-сервер, который служит единой точкой входа в микросервисной архитектуре.
Проблема: у нас есть несколько микросервисов со своим API (возможно на разных протоколах), чтобы начать их использовать мы должны знать все их адреса, в каждом иметь авторизацию и т.п.
Решение: разрабатываем отдельный сервис, который принимает все входящие API запросы и маршрутизирует их соответствующим микросервисам.
Основные функции, которые может выполнять API gateway:
- Маршрутизация (уже проговорили)
- Агрегация данных: объединения запросов и ответов к и от нескольких микросервисов
- Аутентификация: как пример, проверка jwt-токена
- Кеширование
- Логирование и метрики
- Ограничение RPS
- Трансформация протоколов: REST -> gRPC
Также gateway хорошо подходит для миграции с legacy кода, замена монолита микросервисами с сохранением старого API.
Недостатки этого решения:
- Единая точка отказа: gateway упал, вся система стала недоступной
- Бутылочное горлышка: весь трафик идет через один узел.
Последнее о чем хотелось бы поговорить, это сравнение API Gateway, Open Host Service(OHS) и Anti-Corruption Layer (ACL):
- API Gateway: когда нужно упростить доступ к нескольким микросервисам. Например, мобильное приложение.
- OHS: когда ваш контекст должны использовать соседние команды/сервисы. Например, сервис платежей предоставляет API для заказов.
- ACL: при интеграции с legacy-системой или внешним API, для защиты доменной модели.
Пример взаимодействия:
[ Клиент ]
↓ HTTP-запрос
[ API Gateway ] ← Аутентификация, кеширование
↓
[ Сервис A: OHS ] ← Стандартизированный API
↓
[ Сервис B: ACL ] ← Трансформация данных из старой системы
↓
[ Legacy System ]
#patterns #DDD
Telegram
От сердца к коду
Интеграция ограниченных контекстов (часть 2)
В прошлом посте мы разобрали, как быть с общими доменными моделями. В этом же рассмотрим несколько паттернов для ситуаций, когда один ограниченный контекст предоставляет сервис другому.
1️⃣ Anti-Corruption Layer…
В прошлом посте мы разобрали, как быть с общими доменными моделями. В этом же рассмотрим несколько паттернов для ситуаций, когда один ограниченный контекст предоставляет сервис другому.
1️⃣ Anti-Corruption Layer…
🔥3🫡3👍2
Когда мы растем
Здесь я имею ввиду - растем профессионально, как разработчик или менеджер.
Этот пост больше философский, моё имхо на тему.
Ниже я приведу некоторые характеристики правильного вектора своего развития.
1️⃣ Рост там, где тяжело
Очень сложно вырасти в комфорте. Лично для меня комфорт наступает примерно через 3-4 года работы в одном месте. Работа должна быть сложной, непонятной.
Приведу пример самого максимального роста из своего опыта: позвали работать лидом в команду, на новый язык программирования и абсолютно незнакомый мне домен. И через пару месяцев выставили KPI и решили меня уволить :) От KPI я сразу отказался и пошел искать работу, но в итоге, чудесным образом, я остался в компании, стал полноценным лидом, разобрался в домене и ЯП.
2️⃣ Команда
Сильная команда выращивает хороших разработчиков. Сошлюсь на книгу Человеческий фактор, там было исследование что 1 сеньор работает в 4 раза лучше мидла и в 10 раз лучше джуна. Из собственного опыта - примерно согласен.
Ищите команду с сеньорами, но это очень сложно сделать! Ибо, сеньорство в разных компаниях разное, и по своему опыту: чем сильнее руководство - тем сильнее разработка. И наоборот.
Относительно примера выше: я бы не затащил лидство+домен+яп, если бы меня не окружали крутые специалисты. Включая разработку, qa, а особенно ПО, который у меня там был (Кирилл, привет :).
3️⃣ Жопочасы
Как ни крути количество потраченного времени решает. Команда, как собственный тренер, подсказывает направление знаний. Если заниматься одному - придется потратить больше времени на блуждания.
Пример: я сейчас учусь играть на электрогитаре. Прямая зависимость на потраченное время. Если на упражнения на ПШ я трачу 10 минут в день, то и выхлопа будет на 10 минут в день (те кто понял, тот понял). И да, у меня конечно есть преподаватель.
4️⃣ Это невозможно
Такая, мысленная ловушка, когда тебе выпадает какая-то задача и ты сначала думаешь что ее невозможно выполнить. Сейчас, с опытом, вообще нет таких мыслей, есть понимание что сложные задачи требуют больше времени. И всё.
Пример: да, разработка любой вещи, сложной, которую ты никогда не делал. Просто сначала гуглишь, потом ИИ, потом начинаешь собирать все в кучу, дочитываешь доку. И в один день - херак, и всё работает. Проверено уже не 1 раз.
5️⃣ Постоянное обучение
Кто не развивается - тот лох :) Читаем книги, статьи. Смотрим видео, проходим курсы. Пристаем к ИИ с вопросами. Главное не забиваем на свои знания.
6️⃣ Рост незрим в моменте
В моменте ты думаешь, что тебе тяжело, возможно включается синдром самозванца. А спустя некоторое время, смотришь назад - а у тебя всё получилось. Разобрался и в DDD, и вроде как понял как выделять микросервисы, а еще и SDK какой-то напилил.
Наверное пока это всё, что я хотел сказать про рост: тяжело и страшно, крутая команда, постоянное обучение.
#разработка #management #философия
Здесь я имею ввиду - растем профессионально, как разработчик или менеджер.
Этот пост больше философский, моё имхо на тему.
Ниже я приведу некоторые характеристики правильного вектора своего развития.
1️⃣ Рост там, где тяжело
Очень сложно вырасти в комфорте. Лично для меня комфорт наступает примерно через 3-4 года работы в одном месте. Работа должна быть сложной, непонятной.
Приведу пример самого максимального роста из своего опыта: позвали работать лидом в команду, на новый язык программирования и абсолютно незнакомый мне домен. И через пару месяцев выставили KPI и решили меня уволить :) От KPI я сразу отказался и пошел искать работу, но в итоге, чудесным образом, я остался в компании, стал полноценным лидом, разобрался в домене и ЯП.
2️⃣ Команда
Сильная команда выращивает хороших разработчиков. Сошлюсь на книгу Человеческий фактор, там было исследование что 1 сеньор работает в 4 раза лучше мидла и в 10 раз лучше джуна. Из собственного опыта - примерно согласен.
Ищите команду с сеньорами, но это очень сложно сделать! Ибо, сеньорство в разных компаниях разное, и по своему опыту: чем сильнее руководство - тем сильнее разработка. И наоборот.
Относительно примера выше: я бы не затащил лидство+домен+яп, если бы меня не окружали крутые специалисты. Включая разработку, qa, а особенно ПО, который у меня там был (Кирилл, привет :).
3️⃣ Жопочасы
Как ни крути количество потраченного времени решает. Команда, как собственный тренер, подсказывает направление знаний. Если заниматься одному - придется потратить больше времени на блуждания.
Пример: я сейчас учусь играть на электрогитаре. Прямая зависимость на потраченное время. Если на упражнения на ПШ я трачу 10 минут в день, то и выхлопа будет на 10 минут в день (те кто понял, тот понял). И да, у меня конечно есть преподаватель.
4️⃣ Это невозможно
Такая, мысленная ловушка, когда тебе выпадает какая-то задача и ты сначала думаешь что ее невозможно выполнить. Сейчас, с опытом, вообще нет таких мыслей, есть понимание что сложные задачи требуют больше времени. И всё.
Пример: да, разработка любой вещи, сложной, которую ты никогда не делал. Просто сначала гуглишь, потом ИИ, потом начинаешь собирать все в кучу, дочитываешь доку. И в один день - херак, и всё работает. Проверено уже не 1 раз.
5️⃣ Постоянное обучение
Кто не развивается - тот лох :) Читаем книги, статьи. Смотрим видео, проходим курсы. Пристаем к ИИ с вопросами. Главное не забиваем на свои знания.
6️⃣ Рост незрим в моменте
В моменте ты думаешь, что тебе тяжело, возможно включается синдром самозванца. А спустя некоторое время, смотришь назад - а у тебя всё получилось. Разобрался и в DDD, и вроде как понял как выделять микросервисы, а еще и SDK какой-то напилил.
Наверное пока это всё, что я хотел сказать про рост: тяжело и страшно, крутая команда, постоянное обучение.
#разработка #management #философия
🔥8🫡4👍1😢1
Event Sourcing
Это паттерн проектирования, при котором состояние агрегата определяется цепочкой событий. Т.е. вместо сохранения состояние в БД, ES хранит серию неизменяемых событий.
Основные компоненты:
- Событие (Event): факт изменение состояния
- Агрегат (Aggregate): объект чье состояние хранится и восстанавливается из событий
- Event Store: БД оптимизированная для записи событий
- Проекция (Projection): представление агрегата (чтение), построенное из событий
Как это работает?
Изменение состояния:
- Система генерирует событие при любом изменении агрегата
- Событие сохраняется в Event Store
Восстановление состояния:
Чтобы получить текущее состояние агрегата, система последовательно применяет все его события
Создание проекций:
Чтобы каждый раз при чтении не перечитывать всю цепочку событий, их можно переложить в отдельную БД, в более удобный вид. Например в обычную таблицу. Здесь главное, что эту таблицу нельзя изменять, только читать из нее.
Преимущества и недостатки
Расскажу из опыта, мы начинали включать ES для сложных, агрегационных отчетах. Такой отчет очень легко пересчитывался и исправлялся в случае ошибки. Ведь вся история у нас есть, если есть ошибка в алгоритме расчета - просто исправляешь алгоритм и перечитываешь ивенты.
Следовательно преимущества:
- Полный аудит: есть вся история изменений.
- Простая отладка временных ошибок: можно воспроизвести состояние системы на любой момент.
- Очень гибко: легко создавать новые проекции.
При этом нужно было переосмыслить некоторые подходы к архитектуре, разобраться что такое CQRS и как с ним работать.
Я вполне представляю себе историю когда агрегат долго живет, у него очень много ивентов и его восстановление занимает какое-то время. Но это решается снэпшотом агрегата за какое-то стабильное время. Т.е. восстановили состояние агрегата и положили в реляционную БД. Далее при чтении сначала читаем снэпшот, а потом применяем к нему новые ивенты.
Где применять?
Там где важны исторические данные и точность - любые финансовые и медицинские системы.
Немного о технической реализации
Любое изменение агрегата должно генерировать ивент. Ивенты либо складываются во внутреннее поле агрегата, либо возвращаются из метода агрегата. Тут кому что нравится.
В данном случае пусть ивенты хранятся прям внутри агрегата.
Итого: использовать вообще везде ES я бы не стал, но есть некоторые контексты, где с ES жизнь была бы проще. Если уж решили использовать ES, то надо подумать о структуре ивентов, возможно версионировании ивента, миграциях.
#DDD
Это паттерн проектирования, при котором состояние агрегата определяется цепочкой событий. Т.е. вместо сохранения состояние в БД, ES хранит серию неизменяемых событий.
Основные компоненты:
- Событие (Event): факт изменение состояния
{ "type": "OrderCreated", "data": { "id": "123", "total": 100 } }- Агрегат (Aggregate): объект чье состояние хранится и восстанавливается из событий
- Event Store: БД оптимизированная для записи событий
- Проекция (Projection): представление агрегата (чтение), построенное из событий
Как это работает?
Изменение состояния:
- Система генерирует событие при любом изменении агрегата
- Событие сохраняется в Event Store
Восстановление состояния:
Чтобы получить текущее состояние агрегата, система последовательно применяет все его события
func RehydrateOrder(events []Event) *Order {
order := NewOrder()
for _, event := range events {
// Применяем событие к агрегату
order.Apply(event)
}
return order
}Создание проекций:
Чтобы каждый раз при чтении не перечитывать всю цепочку событий, их можно переложить в отдельную БД, в более удобный вид. Например в обычную таблицу. Здесь главное, что эту таблицу нельзя изменять, только читать из нее.
Преимущества и недостатки
Расскажу из опыта, мы начинали включать ES для сложных, агрегационных отчетах. Такой отчет очень легко пересчитывался и исправлялся в случае ошибки. Ведь вся история у нас есть, если есть ошибка в алгоритме расчета - просто исправляешь алгоритм и перечитываешь ивенты.
Следовательно преимущества:
- Полный аудит: есть вся история изменений.
- Простая отладка временных ошибок: можно воспроизвести состояние системы на любой момент.
- Очень гибко: легко создавать новые проекции.
При этом нужно было переосмыслить некоторые подходы к архитектуре, разобраться что такое CQRS и как с ним работать.
Я вполне представляю себе историю когда агрегат долго живет, у него очень много ивентов и его восстановление занимает какое-то время. Но это решается снэпшотом агрегата за какое-то стабильное время. Т.е. восстановили состояние агрегата и положили в реляционную БД. Далее при чтении сначала читаем снэпшот, а потом применяем к нему новые ивенты.
Где применять?
Там где важны исторические данные и точность - любые финансовые и медицинские системы.
Немного о технической реализации
Любое изменение агрегата должно генерировать ивент. Ивенты либо складываются во внутреннее поле агрегата, либо возвращаются из метода агрегата. Тут кому что нравится.
func (o *Order) Cancel() error {
if o.Status == "paid" {
return errors.New("невозможно отменить оплаченный заказ")
}
o.Status = "cancelled"
o.Version++
o.Events = append(o.Events, Event{
Type: "OrderCancelled",
Data: json.RawMessage(`{"reason": "client request"}`),
})
return nil
}В данном случае пусть ивенты хранятся прям внутри агрегата.
Итого: использовать вообще везде ES я бы не стал, но есть некоторые контексты, где с ES жизнь была бы проще. Если уж решили использовать ES, то надо подумать о структуре ивентов, возможно версионировании ивента, миграциях.
#DDD
Telegram
От сердца к коду
Тактические паттерны DDD: Aggregate
Ну вот мы и подошли к самому интересному. Кто-то считает что агрегаты это самое важное в технической части DDD. Кто-то их использует очень редко. Для меня это очень сложное понятие, давайте разбираться.
Если сущность…
Ну вот мы и подошли к самому интересному. Кто-то считает что агрегаты это самое важное в технической части DDD. Кто-то их использует очень редко. Для меня это очень сложное понятие, давайте разбираться.
Если сущность…
🫡3🔥2👍1
Делегирование
Перед любым руководителем, на любом из уровней, лежат какие-то задачи. Часть задач руководитель обязан решать сам, а часть задач нужно уметь делегировать. И умение правильно делегировать это очень важная тема.
Делегировать обязанность ради роста одного из разработчиков - очень полезно, а делегировать только рутину (скучные и низкоприоритетные задачи) - показать разработчику, что его работа не очень важна.
А что если не делегировать совсем? Скорее всего ты сам же и станешь узким местом, команда не будет расти, перестанет хватать времени и начнется выгорание. А еще, как тогда ходить в отпуск?
Для начала давайте разделим свои задачи на три категории:
1. Стратегические задачи. Например, архитектура. Оставляем себе, это наша зона ответственности.
2. Операционные задачи. Например, код-ревью или управление спринтами. Делегируем.
3. Рутина: автоматизируем.
Да, эти три категории относятся к руководителю выше техлида. Хотя, у техлида своя стратегия и операционка.
Ок, мы определились с операционными задачами, теперь надо решить кому их отдать? Было бы неплохо, если бы у нас был сотрудник, у которого сошлись бы сразу три следующих качества:
1. Скилл: хватает компетенций
2. Интерес: у сотрудника присутствует энтузиазм в отношении задачи.
3. Нагрузка: есть время и ресурсы.
Но конечно и из этого правила есть исключения, иногда можно намеренно делегировать задачу человеку с недостающим скиллом, но с высоким потенциалом. Тут просто надо понимать, что на первых порах от нас самих потребуется больше времени на поддержку.
Следующий этап: чётко поставить задачу:
- Цель: почему эта задача важна? Без почему - задача воспринимается как рутина.
- Ожидаемый результат: какой конечный результат вы хотите видеть?
- Сроки: если они есть.
- Ресурсы и полномочия: что есть, чтобы решить задачу. Какие решения сотрудник может принимать сам?
Дальше: не мешать! Не заниматься микроменеджментом! Это не значит, что надо полностью исчезнуть из процесса, быть всегда доступным для консультаций, но не вмешиваться без необходимости.
В одной из книг я прочитал, что делегирование, для лида, это не передача ответственности, а передача исполнения. Поэтому: контролируем, но не мешаем. Например, можно поставить еженедельные созвоны по проблеме, просто раз в день текстом пишет в чате о результатах, или договориться о демонстрации промежуточных результатов.
И последний шаг: после завершения задачи обязательно дать обратную связь сотруднику. Похвалить за успехи и указать что можно улучшить.
В целом я уверен в том, что любой руководитель должен всегда растить себе замену. Делегирование - это один из способов роста сотрудника. Кстати, я не говорил, что взятие дополнительных обязанностей на себя не должно быть отмечено материально. Если делегировали что-то действительно полезное для команды и важное, позаботьтесь и о финансовой мотивации также.
Выше, я написал что код-ревью это операционка. Для меня как для руководителя разработки - это так. А вот как быть с техлидом? Должен ли всегда только он делать код-ревью? Лично я считаю, что нет, часть ревью надо делегировать.
Итого, делегирование – это стратегический инструмент менеджера, а не просто сброс задач. Это инвестиция в рост команды и освобождение своего времени для того, чтобы заниматься стратегически важными задачами.
#management
Перед любым руководителем, на любом из уровней, лежат какие-то задачи. Часть задач руководитель обязан решать сам, а часть задач нужно уметь делегировать. И умение правильно делегировать это очень важная тема.
Делегировать обязанность ради роста одного из разработчиков - очень полезно, а делегировать только рутину (скучные и низкоприоритетные задачи) - показать разработчику, что его работа не очень важна.
А что если не делегировать совсем? Скорее всего ты сам же и станешь узким местом, команда не будет расти, перестанет хватать времени и начнется выгорание. А еще, как тогда ходить в отпуск?
Для начала давайте разделим свои задачи на три категории:
1. Стратегические задачи. Например, архитектура. Оставляем себе, это наша зона ответственности.
2. Операционные задачи. Например, код-ревью или управление спринтами. Делегируем.
3. Рутина: автоматизируем.
Да, эти три категории относятся к руководителю выше техлида. Хотя, у техлида своя стратегия и операционка.
Ок, мы определились с операционными задачами, теперь надо решить кому их отдать? Было бы неплохо, если бы у нас был сотрудник, у которого сошлись бы сразу три следующих качества:
1. Скилл: хватает компетенций
2. Интерес: у сотрудника присутствует энтузиазм в отношении задачи.
3. Нагрузка: есть время и ресурсы.
Но конечно и из этого правила есть исключения, иногда можно намеренно делегировать задачу человеку с недостающим скиллом, но с высоким потенциалом. Тут просто надо понимать, что на первых порах от нас самих потребуется больше времени на поддержку.
Следующий этап: чётко поставить задачу:
- Цель: почему эта задача важна? Без почему - задача воспринимается как рутина.
- Ожидаемый результат: какой конечный результат вы хотите видеть?
- Сроки: если они есть.
- Ресурсы и полномочия: что есть, чтобы решить задачу. Какие решения сотрудник может принимать сам?
Дальше: не мешать! Не заниматься микроменеджментом! Это не значит, что надо полностью исчезнуть из процесса, быть всегда доступным для консультаций, но не вмешиваться без необходимости.
В одной из книг я прочитал, что делегирование, для лида, это не передача ответственности, а передача исполнения. Поэтому: контролируем, но не мешаем. Например, можно поставить еженедельные созвоны по проблеме, просто раз в день текстом пишет в чате о результатах, или договориться о демонстрации промежуточных результатов.
И последний шаг: после завершения задачи обязательно дать обратную связь сотруднику. Похвалить за успехи и указать что можно улучшить.
В целом я уверен в том, что любой руководитель должен всегда растить себе замену. Делегирование - это один из способов роста сотрудника. Кстати, я не говорил, что взятие дополнительных обязанностей на себя не должно быть отмечено материально. Если делегировали что-то действительно полезное для команды и важное, позаботьтесь и о финансовой мотивации также.
Выше, я написал что код-ревью это операционка. Для меня как для руководителя разработки - это так. А вот как быть с техлидом? Должен ли всегда только он делать код-ревью? Лично я считаю, что нет, часть ревью надо делегировать.
Итого, делегирование – это стратегический инструмент менеджера, а не просто сброс задач. Это инвестиция в рост команды и освобождение своего времени для того, чтобы заниматься стратегически важными задачами.
#management
7💯5👍3🫡2
CQRS (часть 1)
Это архитектурный паттерн, который разделяет операций по двум типам:
1. Команды (Commands) - изменение состояния (Create, Update, Delete)
2. Запросы (Queries) - чтение данных (Read)
В CRUD-архитектурах операции чтения и записи часто используют одну и ту же модель данных, по сути один и тот же объект. Но если у нас Event Sourcing или мы оптимизировали запись, но логика чтения оказалось не эффективной для сложных запросов. Или нам надо масштабировать отдельно запись и чтение данных. И вот мы логически дошли до разных моделей данных на чтение и запись.
Еще немного усложним себе жизнь: пусть изменяем данные в одной БД, а читаем из другой. Кажется мы подошли к ключевому вопросу реализации CQRS. Решение:
1. Event Sourcing: write-база = event store (PostgreSQL), а read-модель строится путем применения этих событий и лежит, например в MongoDB (Eventual Consistency).
2. Триггеры на таблицах write-базы генерят события при изменении и обновляют read-базу
3. Domain Events, приложение явно генерирует события, отдельный процесс обрабатывает события и обновляет read-базу.
Давайте более внимательно остановимся на Event Sourcing и реализуем полный цикл на примере создания задачи.
Команда:
Обработчик:
Структура таблицы Event Store (PostgreSQL):
Сохраняем ивент:
Пришло время обработать новое событие (projection), для этого у нас будет отдельный worker (процесс):
Обновляем read-модель:
С обновлением понятно как продолжать - нужно добавить метод handleTaskUpdated, который найдем в монге правильную запись и относительно ивента domain.TaskUpdatedEvent обновит поля в ней.
to be continued...
#DDD
Это архитектурный паттерн, который разделяет операций по двум типам:
1. Команды (Commands) - изменение состояния (Create, Update, Delete)
2. Запросы (Queries) - чтение данных (Read)
В CRUD-архитектурах операции чтения и записи часто используют одну и ту же модель данных, по сути один и тот же объект. Но если у нас Event Sourcing или мы оптимизировали запись, но логика чтения оказалось не эффективной для сложных запросов. Или нам надо масштабировать отдельно запись и чтение данных. И вот мы логически дошли до разных моделей данных на чтение и запись.
Еще немного усложним себе жизнь: пусть изменяем данные в одной БД, а читаем из другой. Кажется мы подошли к ключевому вопросу реализации CQRS. Решение:
1. Event Sourcing: write-база = event store (PostgreSQL), а read-модель строится путем применения этих событий и лежит, например в MongoDB (Eventual Consistency).
2. Триггеры на таблицах write-базы генерят события при изменении и обновляют read-базу
3. Domain Events, приложение явно генерирует события, отдельный процесс обрабатывает события и обновляет read-базу.
Давайте более внимательно остановимся на Event Sourcing и реализуем полный цикл на примере создания задачи.
Команда:
POST /tasks
{
"title": "Разработать архитектуру",
"description": "Спроектировать систему на CQRS+ES"
}
Обработчик:
func (h *CreateTaskHandler) Handle(cmd CreateTaskCommand) error {
event := domain.NewTaskCreatedEvent(
uuid.New().String(),
cmd.Title,
cmd.Description,
time.Now(),
)
return h.eventStore.Append(event)
}Структура таблицы Event Store (PostgreSQL):
CREATE TABLE events (
id SERIAL PRIMARY KEY,
aggregate_id VARCHAR(255) NOT NULL, -- ID задачи
type VARCHAR(255) NOT NULL, -- Тип события
data JSONB NOT NULL, -- Данные события
version INT NOT NULL, -- Версия для оптимистичной блокировки
timestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON events (aggregate_id);
Сохраняем ивент:
INSERT INTO events (aggregate_id, type, data, version, timestamp)
VALUES (
'task-123',
'TaskCreated',
'{
"title": "Разработать архитектуру",
"description": "Спроектировать систему на CQRS+ES"
}',
1, -- начальная версия
NOW()
);
Пришло время обработать новое событие (projection), для этого у нас будет отдельный worker (процесс):
func (p *TaskProjection) Start() {
lastPosition := p.GetLastProcessedPosition()
for {
events := p.eventStore.LoadEvents(lastPosition, 100)
if len(events) == 0 {
time.Sleep(1 * time.Second)
continue
}
for _, event := range events {
p.HandleEvent(event)
lastPosition = event.ID
}
p.SavePosition(lastPosition)
}
}
func (p *TaskProjection) HandleEvent(event domain.Event) {
switch e := event.(type) {
case *domain.TaskCreatedEvent:
p.handleTaskCreated(e)
case *domain.TaskUpdatedEvent:
p.handleTaskUpdated(e)
case *domain.TaskCompletedEvent:
p.handleTaskCompleted(e)
}
}Обновляем read-модель:
func (p *TaskProjection) handleTaskCreated(event *domain.TaskCreatedEvent) {
doc := bson.M{
"_id": event.AggregateID,
"title": event.Title,
"description": event.Description,
"status": "new",
"created_at": event.Timestamp,
"updated_at": event.Timestamp,
"version": 1,
}
_, err := p.mongoCollection.InsertOne(context.Background(), doc)
if err != nil {
log.Printf("Ошибка вставки: %v", err)
}
}С обновлением понятно как продолжать - нужно добавить метод handleTaskUpdated, который найдем в монге правильную запись и относительно ивента domain.TaskUpdatedEvent обновит поля в ней.
to be continued...
#DDD
Telegram
От сердца к коду
Event Sourcing
Это паттерн проектирования, при котором состояние агрегата определяется цепочкой событий. Т.е. вместо сохранения состояние в БД, ES хранит серию неизменяемых событий.
Основные компоненты:
- Событие (Event): факт изменение состояния { "type":…
Это паттерн проектирования, при котором состояние агрегата определяется цепочкой событий. Т.е. вместо сохранения состояние в БД, ES хранит серию неизменяемых событий.
Основные компоненты:
- Событие (Event): факт изменение состояния { "type":…
🔥5🫡2
CQRS (часть 2)
О чем еще можно подумать?
- идемпотентность обработки ивентов
- пересборка read-модели
- А что если нам понадобится еще одна read-модель - без проблем, добавляем новую проекцию, при это не трогаем код команд.
Давайте еще посмотрим, пример структуры директорий приложения:
Кроме этого есть еще целые теории о том может ли команда возвращать результат или только ошибку? (имхо, может) Тут лучше вам самим углубиться в понимание этого паттерна и посмотреть на детали подробнее.
Итого, CQRS подходит для сложных систем с большим количеством бизнес-логики. Для приложений с высокой нагрузкой, где чтение и запись нужно масштабировать независимо.
Читаем базу:
- https://martinfowler.com/bliki/CQRS.html
- https://learn.microsoft.com/ru-ru/azure/architecture/patterns/cqrs
#DDD
О чем еще можно подумать?
- идемпотентность обработки ивентов
func (p *TaskProjection) HandleEvent(event domain.Event) {
// Проверяем, не обрабатывали ли мы уже это событие
if p.IsEventProcessed(event.ID) {
return
}
// ... логика обработки ...
p.MarkEventProcessed(event.ID)
}- пересборка read-модели
func (p *TaskProjection) Rebuild() {
// 1. Удаляем все данные в MongoDB
p.mongoCollection.DeleteMany({})
// 2. Читаем все события с начала
events := p.eventStore.LoadAllEvents()
// 3. Применяем события по порядку
for _, event := range events {
p.HandleEvent(event)
}
}- А что если нам понадобится еще одна read-модель - без проблем, добавляем новую проекцию, при это не трогаем код команд.
Давайте еще посмотрим, пример структуры директорий приложения:
cmd/
internal/
├── task/
│ ├── domain/
│ │ ├── task.go
│ │ └── events/
│ │ │ └── task_created.go
│ ├── application/
│ │ ├── commands/
│ │ │ └── create_task.go
│ │ ├── queries/
│ │ │ └── get_tasks.go
│ │ └── projections/
│ │ │ └── task_projection.go
└── infrastructure/
├── read_db/ # Репа для чтения
└── write_db/ # Репа для записи
Кроме этого есть еще целые теории о том может ли команда возвращать результат или только ошибку? (имхо, может) Тут лучше вам самим углубиться в понимание этого паттерна и посмотреть на детали подробнее.
Итого, CQRS подходит для сложных систем с большим количеством бизнес-логики. Для приложений с высокой нагрузкой, где чтение и запись нужно масштабировать независимо.
Читаем базу:
- https://martinfowler.com/bliki/CQRS.html
- https://learn.microsoft.com/ru-ru/azure/architecture/patterns/cqrs
#DDD
martinfowler.com
bliki: CQRS
CQRS (Command Query Responsibility Segregation) is the notion that you can use a different model to update information than the model you use to read information
🔥5🫡3
Модель зрелости команды
Процессы в командах и сами команды должны всегда расти. Как раз для этих целей и существует модель зрелости, которая:
1. Позволяет провести диагностику команд
2. Задает roadmap для развития команд
Эта модель помогает понять текущее состояние и целенаправленно двигаться к большей эффективности и предсказуемости. Впервые услышал об этом от своего друга, который работает в бигтехе, и пошел разбираться что же это такое. Кажется что небольшим кампаниям или стартапам эта модель не нужна, хотя посмотреть как сделано "у больших" будет очень полезно.
Итак, в чем же суть? У кампании есть видение идеальной команды: процессы, проактивность и т.п. И есть некоторые последовательные шаги (уровни), характеристики команды на этом уровне и задачи, которые команда должна выполнять, чтобы перейти на следующий уровень зрелости. Например:
🔸Уровень 1: Начальный. Хаос, отсутствие процессов , нет стандартов тестирование и написания кода, игнор техдолга. Лид занимается тушением пожаров. Техническая аналогия: монолит со спагетти-кодом.
🔸Уровень 2: Появляются базовые процессы (Scrum/Kanban), но они еще хрупкие. Появляются стандарты кода, CI/CD, но тестирование не полное. Коммуникации налажены, но не всегда эффективны. Команда предсказуема в спокойных условиях. Лид поддерживает процессы, контролирует качество и сроки и начинает делегировать операционку. Техническая аналогия: появились модули, интерфейсы, есть базовые юнит-тесты.
🔸Уровень 3: Стабильные, принятые командой процессы, высокий уровень автоматизации. Техдолг разбирается системно, коммуникации эффективны. Лид занимается развитием людей (менторство), фасилитация. Техническая аналогия: DDD, Чистая архитектура, слабая связанность и явные контракты. Полный набор автоматических тестов.
🔸Уровень 4: Процессы измеряются и постоянно улучшаются командой (вводятся метрики качества, скорости). Превентивная работа с рисками и техдолгом. Высокая автономность и ответственность. Команда сама идентифицирует проблемы и предлагает решения. Сильная культура feedback и обучения. Лид создает среду для роста, защищает команду от внешнего хаоса и развивает инженерную культуру. Техническая аналогия: продвинутый мониторинг, алерты, автоматическое масштабирование и постоянный рефакторинг как часть процесса.
🔸Уровень 5: Инновации. Команда проактивно ищет способы улучшить продукт, процессы и технологии. Экспериментирование, глубокая экспертиза в домене и технологиях. Сильная внутренняя мотивация. Лид занимается видением, построением связей, привлечением лучших талантов и защищает среду команды. Техническая аналогия: можно подумать про оупенсорс-вклад.
Для начала определяем текущий уровень, а дальше уже лид должен начинать работать с командой для перевода на следующий. Это не быстрый процесс, но у нас уже есть роадмап, не забиваем, идем по нему.
Очень хорошо описано и есть пример уровней зрелости в Авито: https://habr.com/ru/companies/avito/articles/578342/
#management
Процессы в командах и сами команды должны всегда расти. Как раз для этих целей и существует модель зрелости, которая:
1. Позволяет провести диагностику команд
2. Задает roadmap для развития команд
Эта модель помогает понять текущее состояние и целенаправленно двигаться к большей эффективности и предсказуемости. Впервые услышал об этом от своего друга, который работает в бигтехе, и пошел разбираться что же это такое. Кажется что небольшим кампаниям или стартапам эта модель не нужна, хотя посмотреть как сделано "у больших" будет очень полезно.
Итак, в чем же суть? У кампании есть видение идеальной команды: процессы, проактивность и т.п. И есть некоторые последовательные шаги (уровни), характеристики команды на этом уровне и задачи, которые команда должна выполнять, чтобы перейти на следующий уровень зрелости. Например:
🔸Уровень 1: Начальный. Хаос, отсутствие процессов , нет стандартов тестирование и написания кода, игнор техдолга. Лид занимается тушением пожаров. Техническая аналогия: монолит со спагетти-кодом.
🔸Уровень 2: Появляются базовые процессы (Scrum/Kanban), но они еще хрупкие. Появляются стандарты кода, CI/CD, но тестирование не полное. Коммуникации налажены, но не всегда эффективны. Команда предсказуема в спокойных условиях. Лид поддерживает процессы, контролирует качество и сроки и начинает делегировать операционку. Техническая аналогия: появились модули, интерфейсы, есть базовые юнит-тесты.
🔸Уровень 3: Стабильные, принятые командой процессы, высокий уровень автоматизации. Техдолг разбирается системно, коммуникации эффективны. Лид занимается развитием людей (менторство), фасилитация. Техническая аналогия: DDD, Чистая архитектура, слабая связанность и явные контракты. Полный набор автоматических тестов.
🔸Уровень 4: Процессы измеряются и постоянно улучшаются командой (вводятся метрики качества, скорости). Превентивная работа с рисками и техдолгом. Высокая автономность и ответственность. Команда сама идентифицирует проблемы и предлагает решения. Сильная культура feedback и обучения. Лид создает среду для роста, защищает команду от внешнего хаоса и развивает инженерную культуру. Техническая аналогия: продвинутый мониторинг, алерты, автоматическое масштабирование и постоянный рефакторинг как часть процесса.
🔸Уровень 5: Инновации. Команда проактивно ищет способы улучшить продукт, процессы и технологии. Экспериментирование, глубокая экспертиза в домене и технологиях. Сильная внутренняя мотивация. Лид занимается видением, построением связей, привлечением лучших талантов и защищает среду команды. Техническая аналогия: можно подумать про оупенсорс-вклад.
Для начала определяем текущий уровень, а дальше уже лид должен начинать работать с командой для перевода на следующий. Это не быстрый процесс, но у нас уже есть роадмап, не забиваем, идем по нему.
Очень хорошо описано и есть пример уровней зрелости в Авито: https://habr.com/ru/companies/avito/articles/578342/
#management
Хабр
Модель зрелости: как оценивать и растить инженерные команды
Мы продолжаем делиться внутренними документами Авито. Сегодня это будет модель зрелости. Она может пригодиться как трекер внедрения инженерных практик всем компаниям, где есть своя разработка. Внутри...
🔥5🫡1
Гексагональная архитектура (ports & adapters)
Давайте сделаем шаг назад. Прежде мы разобрали CQRS, как подход к организации архитектуры. Довольно непростой подход, для сложной бизнес-логики. А что если нам надо что-то попроще, но при этом хорошо ложащееся на DDD и Чистую архитектуру? - Гексагональная архитектура или ports & adapters.
В конце статьи будет несколько ссылок на хабр, там можно будет подробнее прочитать, а в этой статье будут лишь некоторые пояснения:
- Порты - это интерфейсы, вот в прямом смысле слова: type Reader interface {} - это порт. Некий контракт.
- Адаптеры - это реализация интерфейсов. Например, репозитории или клиент который ходит в какой-то внешний сервис.
- Ядро - чистая бизнес-логика, доменная модель. Например, расчет налогов, комиссий. Ничего не знает про внешние зависимости. Реализуется через агрегат, entity, value object, domain service. На этом уровне используются порты.
Преимущества:
- Ядро чистое и независимое от инфраструктуры.
- Легкая смена инфраструктурных компонентов, например, PostgreSQL на Mongo. Просто реализуем адаптер на нужный порт репозитория.
- Тестируемость. Всё ядро несложно закрыть тестами, мокаем порты, тестируем на моках. Репозитории тестируем на отдельной тестовой репе, HTTP-handler тестируем отдельно мокая сервисы.
- Четкое разделение ответственности: понятно что где должно лежать.
- Легко поддается эволюции, отлично сочетается с DDD и Чистой архитектурой, позволяет легко ограничивать контекст. Да и качественно продуманная гексагональная архитектура является фундаментом для перехода на микросервисы (ядро = потенциальный сервис).
- Кстати, DDD не запрещает в разных ограниченных контекстах иметь разные архитектурные подходы. Где-то гексагон, а где-то CQRS.
Лично моё мнение: везде, где CQRS слишком сложный, нужно использовать гексагон.
Ну и последнее - давайте посмотрим на структуру директорий для проекта на golang:
Ну и в конце , что бы почитать: https://habr.com/ru/companies/timeweb/articles/771338/
Давайте сделаем шаг назад. Прежде мы разобрали CQRS, как подход к организации архитектуры. Довольно непростой подход, для сложной бизнес-логики. А что если нам надо что-то попроще, но при этом хорошо ложащееся на DDD и Чистую архитектуру? - Гексагональная архитектура или ports & adapters.
В конце статьи будет несколько ссылок на хабр, там можно будет подробнее прочитать, а в этой статье будут лишь некоторые пояснения:
- Порты - это интерфейсы, вот в прямом смысле слова: type Reader interface {} - это порт. Некий контракт.
- Адаптеры - это реализация интерфейсов. Например, репозитории или клиент который ходит в какой-то внешний сервис.
- Ядро - чистая бизнес-логика, доменная модель. Например, расчет налогов, комиссий. Ничего не знает про внешние зависимости. Реализуется через агрегат, entity, value object, domain service. На этом уровне используются порты.
Преимущества:
- Ядро чистое и независимое от инфраструктуры.
- Легкая смена инфраструктурных компонентов, например, PostgreSQL на Mongo. Просто реализуем адаптер на нужный порт репозитория.
- Тестируемость. Всё ядро несложно закрыть тестами, мокаем порты, тестируем на моках. Репозитории тестируем на отдельной тестовой репе, HTTP-handler тестируем отдельно мокая сервисы.
- Четкое разделение ответственности: понятно что где должно лежать.
- Легко поддается эволюции, отлично сочетается с DDD и Чистой архитектурой, позволяет легко ограничивать контекст. Да и качественно продуманная гексагональная архитектура является фундаментом для перехода на микросервисы (ядро = потенциальный сервис).
- Кстати, DDD не запрещает в разных ограниченных контекстах иметь разные архитектурные подходы. Где-то гексагон, а где-то CQRS.
Лично моё мнение: везде, где CQRS слишком сложный, нужно использовать гексагон.
Ну и последнее - давайте посмотрим на структуру директорий для проекта на golang:
project-name/
├── cmd/ # Точки входа в приложение
│ └── api/
│ └── main.go
├── internal/ # Внутренний код приложения
│ ├── core/ # Бизнес-логика (домен)
│ │ ├── domain/ # Доменные модели
│ │ │ ├── user.go
│ │ │ └── product.go
│ │ ├── ports/ # Порты (интерфейсы)
│ │ │ ├── input/ # Входные порты (primary/driving)
│ │ │ │ └── service.go
│ │ │ └── output/ # Выходные порты (secondary/driven)
│ │ │ └── repository.go
│ │ └── services/ # Реализация сервисов
│ │ └── user_service.go
│ ├── adapters/ # Адаптеры
│ │ ├── primary/ # Входные адаптеры
│ │ │ ├── http/ # HTTP handlers
│ │ │ └── grpc/ # gRPC handlers
│ │ └── secondary/ # Выходные адаптеры
│ │ ├── postgres/ # PostgreSQL репозиторий
│ │ └── redis/ # Redis кэш
│ └── config/ # Конфигурация приложения
│ └── config.go
├── pkg/ # Публичные пакеты
│ └── common/ # Общие утилиты
├── api/ # API документация
│ └── swagger/
├── test/ # Тесты
│ ├── integration/
│ └── e2e/
├── docs/ # Документация проекта
├── scripts/ # Скрипты для сборки и развертывания
├── go.mod
├── go.sum
├── Makefile
└── README.md
Ну и в конце , что бы почитать: https://habr.com/ru/companies/timeweb/articles/771338/
Telegram
От сердца к коду
Антипаттерны доменной модели
🔸Анемичная модель (Anemic Domain Model)
Сущность не содержит никакой бизнес-логики (методов). По сути, сущность используется как DTO.
Это влечет за собой нарушение инкапсуляции, логика уезжает на прикладной уровень.
🔸ORM-анемия…
🔸Анемичная модель (Anemic Domain Model)
Сущность не содержит никакой бизнес-логики (методов). По сути, сущность используется как DTO.
Это влечет за собой нарушение инкапсуляции, логика уезжает на прикладной уровень.
🔸ORM-анемия…
👍6🫡4❤2😢1
Гексагональная vs Чистая vs CQRS
Давайте коротенько подведем итог разным видам архитектурных подходов.
Гексагональная или Чистая архитектуры. Для меня это синонимы, чистая - немного переосмысленная гексагональная. Основная идея в том, чтобы сделать приложение независимым от инфраструктуры. Я уже говорил что везде где не CQRS использую гексагон (чистую):
- Изолирую доменный слой
- Придерживаюсь SOLID
- Перекрываюсь везде интерфейсами
- Пишу изолированные тесты по слоям
CQRS - разделение операций чтения и записи. Основная идея в том, что чтение и запись могут использовать разные модели данных и обрабатываться отдельно. Причины использования:
- Денежные или медицинские данные
- Появляются проблемы с производительностью на чтение или запись
- Есть очень сложные запросы
- Нужна подробная история изменений
- Принимаем Eventual Consistency
Эти подходы не взаимоисключающие, их можно комбинировать:
- Также изолируем бизнес-логику в доменном слое
- Адаптеры работают на инфраструктурном слое и реализуют порты
- CQRS работает на application уровне
#DDD
Давайте коротенько подведем итог разным видам архитектурных подходов.
Гексагональная или Чистая архитектуры. Для меня это синонимы, чистая - немного переосмысленная гексагональная. Основная идея в том, чтобы сделать приложение независимым от инфраструктуры. Я уже говорил что везде где не CQRS использую гексагон (чистую):
- Изолирую доменный слой
- Придерживаюсь SOLID
- Перекрываюсь везде интерфейсами
- Пишу изолированные тесты по слоям
CQRS - разделение операций чтения и записи. Основная идея в том, что чтение и запись могут использовать разные модели данных и обрабатываться отдельно. Причины использования:
- Денежные или медицинские данные
- Появляются проблемы с производительностью на чтение или запись
- Есть очень сложные запросы
- Нужна подробная история изменений
- Принимаем Eventual Consistency
Эти подходы не взаимоисключающие, их можно комбинировать:
- Также изолируем бизнес-логику в доменном слое
- Адаптеры работают на инфраструктурном слое и реализуют порты
- CQRS работает на application уровне
#DDD
Telegram
От сердца к коду
Гексагональная архитектура (ports & adapters)
Давайте сделаем шаг назад. Прежде мы разобрали CQRS, как подход к организации архитектуры. Довольно непростой подход, для сложной бизнес-логики. А что если нам надо что-то попроще, но при этом хорошо ложащееся…
Давайте сделаем шаг назад. Прежде мы разобрали CQRS, как подход к организации архитектуры. Довольно непростой подход, для сложной бизнес-логики. А что если нам надо что-то попроще, но при этом хорошо ложащееся…
❤3👍2🫡2
Оставить команду или менять?
Давайте обсудим ситуацию: вы выходите на позицию лида команды — что делать с текущим составом? Удерживать всех или менять? Пройдемся по вариантам.
1️⃣ Удерживаем людей любыми силами.
Раньше я был поклонником теории Ицхака Адизеса, если кратко, то он считает, что сила команды в ее разнообразии. В разнообразии людей разных архетипов: кто-то доводит задачу до конца, кто-то смотрит вперед и в будущее, кто-то бюрократ (в хорошем смысле этого слова), а у кого-то просто высокие софт скиллы.
Даже на наших собеседованиях на работу я пользовался этой теорией и давал шанс людям сильно отличающимся от меня. Соответственно, отталкиваясь от этого, надо стараться удержать разных людей в своей команде, стараться найти с ними общий язык, договориться.
И совсем недавно я понял, а потом и подтвердил в выводах некоторых книг по менеджменту, что это плохой путь, по крайней мере лично у меня в команде он не работает. Хотите вот такой солянки из разных людей - готовьтесь постоянно спорить по любой мелочи. По сути команда вроде бы и работает, но лид тратит кучу времени на убеждение, лишнее нытье и споры. Лично я понял, что это не мой путь.
2️⃣ Заменить всех.
На мой взгляд, это другая крайность. Рискуем и разработкой, и репутацией компании. Быстрая замена остановит поставку — новых ещё надо нанять и онбордить. Кажется очевидным: это не наш путь.
3️⃣ Частичная замена.
Вот оно! Чувствуете?) Вы приходите в уже состоявшуюся команду со своими требованиями и видением процессов разработки. Вы лид, вы должны, как минимум технически вести команду, а кроме того и постоянно следить за состоянием людей. Вы начинаете заводить свои процессы, сталкиваетесь со спорами и сопротивлением, но вы не отступаете и постепенно проводите то, что вы считаете важным. Половина команды принимает новые правила и требования, другая часть пытается их саботировать. Несколько раз вы пытаетесь их убедить, объяснить, почему следовать новым требованиям это важно, но они то лучше знают, как это надо делать или как они привыкли.
А дальше все просто: обращаетесь к местным HR, открываете вакансию на подбор разработчика и начинаете постепенно менять саботажников на новых людей. Новых с первого дня погружаете в процессы и культуру команды.
Выводы:
- Замена лидера почти всегда ведёт к частичной замене команды — и это нормально, если цель не «заменить людей», а повысить соответствие целям кампании.
- При приеме на работу лида в команду нужно сразу уточнять вопрос об увольнении/найме
- Не тратьте много времени на убеждение саботажников
- Подбирайте в свою команду людей с общими для вас ценностями и идеями, не только по техническим скиллам.
Давайте обсудим ситуацию: вы выходите на позицию лида команды — что делать с текущим составом? Удерживать всех или менять? Пройдемся по вариантам.
1️⃣ Удерживаем людей любыми силами.
Раньше я был поклонником теории Ицхака Адизеса, если кратко, то он считает, что сила команды в ее разнообразии. В разнообразии людей разных архетипов: кто-то доводит задачу до конца, кто-то смотрит вперед и в будущее, кто-то бюрократ (в хорошем смысле этого слова), а у кого-то просто высокие софт скиллы.
Даже на наших собеседованиях на работу я пользовался этой теорией и давал шанс людям сильно отличающимся от меня. Соответственно, отталкиваясь от этого, надо стараться удержать разных людей в своей команде, стараться найти с ними общий язык, договориться.
И совсем недавно я понял, а потом и подтвердил в выводах некоторых книг по менеджменту, что это плохой путь, по крайней мере лично у меня в команде он не работает. Хотите вот такой солянки из разных людей - готовьтесь постоянно спорить по любой мелочи. По сути команда вроде бы и работает, но лид тратит кучу времени на убеждение, лишнее нытье и споры. Лично я понял, что это не мой путь.
2️⃣ Заменить всех.
На мой взгляд, это другая крайность. Рискуем и разработкой, и репутацией компании. Быстрая замена остановит поставку — новых ещё надо нанять и онбордить. Кажется очевидным: это не наш путь.
3️⃣ Частичная замена.
Вот оно! Чувствуете?) Вы приходите в уже состоявшуюся команду со своими требованиями и видением процессов разработки. Вы лид, вы должны, как минимум технически вести команду, а кроме того и постоянно следить за состоянием людей. Вы начинаете заводить свои процессы, сталкиваетесь со спорами и сопротивлением, но вы не отступаете и постепенно проводите то, что вы считаете важным. Половина команды принимает новые правила и требования, другая часть пытается их саботировать. Несколько раз вы пытаетесь их убедить, объяснить, почему следовать новым требованиям это важно, но они то лучше знают, как это надо делать или как они привыкли.
А дальше все просто: обращаетесь к местным HR, открываете вакансию на подбор разработчика и начинаете постепенно менять саботажников на новых людей. Новых с первого дня погружаете в процессы и культуру команды.
Выводы:
- Замена лидера почти всегда ведёт к частичной замене команды — и это нормально, если цель не «заменить людей», а повысить соответствие целям кампании.
- При приеме на работу лида в команду нужно сразу уточнять вопрос об увольнении/найме
- Не тратьте много времени на убеждение саботажников
- Подбирайте в свою команду людей с общими для вас ценностями и идеями, не только по техническим скиллам.
👍7👏2🫡2
Компромиссы
Было ли у вас такое, что придя на новую работу, вы понимали, что попали в очень слабую команду? На собесе все выглядели сеньорами, а по факту и говнокод и кривые процессы?
Если вы зашли сеньором - не тратьте время, просто сразу уходите на рынок. С этой должности очень трудно менять ситуацию, придется слишком много всем доказывать, что ты не дурак.
А вот что делать, если вы зашли лидом?
1. Если вы еще на собеседовании не уточнили, насколько сильно можете влиять на процессы и команду - уточните это прямо сейчас (увольнение, влияние на ЗП, изменение процессов).
2. Начинайте выстраивать процессы, смотреть на команду, находить тех кто надежнее и делегировать.
3. Не идите на компромиссы! По-другому, не соглашайтесь на худшее, когда вы знаете, как сделать лучше. Или - не понижайте планку своего качества. Вот тут и остановимся подробнее, ради этого пункта и писался весь пост.
Не идите на компромиссы с самим собой: не отдавайте плохой код в ревью, пока пускай так, а потом поправлю, когда время будет. И не идите на компромиссы в отношении кого-то: например при ревью чьего-то кода, не делайте поблажек, это медвежья услуга.
У меня был опыт, когда я несколько лет пытался вырастить разработчика, а он продолжал делать одни и те же ошибки, а я всё ждал: ну вот следующий МР будет лучше. Не будет. Во-первых, сразу показываем новой команде новые стандарты работы. Во-вторых, лучше сразу жестко обозначить свои требования к коду.
Обратной стороной "не хождения на компромиссы" может стать токсичная и пассивно-агрессивная обратная связь. Признаюсь, бывало и у меня, приходилось извиняться. Прикрываясь компромиссами можно перейти черту адекватного фидбэка. В целом здоровой обратной связи нужно еще поучиться.
4. Пишем доку "Требования к качеству разработки", в которой расписываем:
- чеклист на ревью
- code style
- документирование кода
- требования к написанию тестов и рефакторингу
Отдаем всё это добро на ознакомление команде, предупреждаем, что есть пару недель на разгон, а далее МРы будут отклоняться. За это время впиливаем в CICD нужные пайплайны и уже начинаем ревьюить, а кода пайплайны готовы - останавливаем деплой.
Далее команда начинает зреть, в ней появятся люди, которым можно доверять и делегировать качественное ревью, ну а тех кто саботирует новые стандарты - мы не держим.
#management
Было ли у вас такое, что придя на новую работу, вы понимали, что попали в очень слабую команду? На собесе все выглядели сеньорами, а по факту и говнокод и кривые процессы?
Если вы зашли сеньором - не тратьте время, просто сразу уходите на рынок. С этой должности очень трудно менять ситуацию, придется слишком много всем доказывать, что ты не дурак.
А вот что делать, если вы зашли лидом?
1. Если вы еще на собеседовании не уточнили, насколько сильно можете влиять на процессы и команду - уточните это прямо сейчас (увольнение, влияние на ЗП, изменение процессов).
2. Начинайте выстраивать процессы, смотреть на команду, находить тех кто надежнее и делегировать.
3. Не идите на компромиссы! По-другому, не соглашайтесь на худшее, когда вы знаете, как сделать лучше. Или - не понижайте планку своего качества. Вот тут и остановимся подробнее, ради этого пункта и писался весь пост.
Не идите на компромиссы с самим собой: не отдавайте плохой код в ревью, пока пускай так, а потом поправлю, когда время будет. И не идите на компромиссы в отношении кого-то: например при ревью чьего-то кода, не делайте поблажек, это медвежья услуга.
У меня был опыт, когда я несколько лет пытался вырастить разработчика, а он продолжал делать одни и те же ошибки, а я всё ждал: ну вот следующий МР будет лучше. Не будет. Во-первых, сразу показываем новой команде новые стандарты работы. Во-вторых, лучше сразу жестко обозначить свои требования к коду.
Обратной стороной "не хождения на компромиссы" может стать токсичная и пассивно-агрессивная обратная связь. Признаюсь, бывало и у меня, приходилось извиняться. Прикрываясь компромиссами можно перейти черту адекватного фидбэка. В целом здоровой обратной связи нужно еще поучиться.
4. Пишем доку "Требования к качеству разработки", в которой расписываем:
- чеклист на ревью
- code style
- документирование кода
- требования к написанию тестов и рефакторингу
Отдаем всё это добро на ознакомление команде, предупреждаем, что есть пару недель на разгон, а далее МРы будут отклоняться. За это время впиливаем в CICD нужные пайплайны и уже начинаем ревьюить, а кода пайплайны готовы - останавливаем деплой.
Далее команда начинает зреть, в ней появятся люди, которым можно доверять и делегировать качественное ревью, ну а тех кто саботирует новые стандарты - мы не держим.
#management
1👍9🔥3🫡3❤1