И для монолитов такое разбухание и усложнение со временем абсолютно естественный процесс, который, разумеется, создаёт сложности. Но эта проблема возникает вовсе не из-за "жирных" интерфейсов, а из-за природы монолита.
Согласен, но разрабатывать вполне пристойные монолиты, не превращая их в запущенную заводь — это выгодная стратегия с самого начала.
Думаю, что это вопрос популяризации в команде необходимых подходов и поиска компромиссов. При понятной доменной области и адекватных сроках не составляет труда сразу выделить пакеты для user, order и basket, описывать в каждом из них изолированные usecase, которые используют понятные и «легкие» интерфейсы.
Такой монолит будет by design модульным и его будет приятно распиливать на сервисы. А если не доживём до распила, то хотя бы позывов писать статьи не будет :)
Благодарю за конструктивный и развернутый комментарий.
На мой взгляд вы привели пример скрытой передачи транзакции, а затем сказали что в целом этот подход антипатерн потому что выполняется не явно, через контекс, и что может привести к утечкам.
Уточню, чтобы избежать недопонимания. Я не хотел в статье прямо называть это антипаттерном, чтобы не вызывать ненужной эмоциональной реакции у читателей, так как понимаю, что менеджер транзакций — это скорее стандартный подход, которым пользуются разработчики. В статье я хотел подчеркнуть возможные последствия его использования, с которыми сталкивался на личном опыте, а также описать альтернативу и объяснить, почему считаю ее более корректной. А вот в комментариях, да, я сделал такое утверждение.
И это может сделать менеджер транзакций. Меня смущает, что ваше решение добавляет лишней сложности, ну нет транзакций у файлика, назовите менеджер ACID операций. Но вам в любом случае нужна управляющая конструкция которая следит и принимает решение об комитете или откате(перезаписи).
Я согласен с тезисом о том, что на уровне usecase возможна абстракция, описывающая именно «бизнес-транзакцию», и, соответственно, сущность менеджера для них. Важный нюанс — определение необходимых гарантий и свойств этих «бизнес-транзакций», а также их реализация.
Я не согласен с тем, что на уровень usecase корректно пробрасывать ACID-транзакцию и менеджер для ACID-транзакций — об этом я и пишу в статье. Это создает жесткую связь между конкретной технологией и кодовой базой, усложняет управление транзакциями и дает возможность использовать ACID-транзакции некорректно, то есть не так, как изначально предполагали разработчики СУБД.
Ваше предложение хорошо работает в монолите, но если у нас распределеный модности (несколько репозиториев) и мне нужно вызвать 6 из них, мне придется создать транзакцию в первом и передавать ее через сервис дальше, а кто отвечать будет за коммит?
В этом контексте речь идет как раз о «бизнес-транзакциях», т.е. о сагах, которые упомянул @powerman.
С моей точки зрения, если у вас есть необходимость в сагах, то менеджер саг будет находиться слоем выше и, скорее всего, оперировать usecase как этапами выполнения саги. В этом случае ACID-транзакция также остается деталью реализации репозитория и не проникает на уровень usecase.
Благодарю за конструктивный и развернутый комментарий.
Но в коде примерах видно что все экспортируемое и даже интерфейсы по месту использования.
Действительно, интерфейс публичный, но речь скорее о том, что не стоит экспортировать чужие интерфейсы из соседних usecase. Возможно, формулировка была неудачной, но именно это я и имел в виду.
Если как вы описали, то это по факту не репозиторий, а хранилище — storage. Абстрактный слой хранения данных, и неважно, в БД в виде таблиц или в файлах и так далее.
Хм, спасибо за замечание. Возможно, проблема в расхождении терминологии. В статье я старался раскрыть термин «репозиторий» и требования, которые к нему предъявляются.
Несколько раз пробовал на го сделать классический ДДД по Эвансу, и постоянно получалось какая-то каша из функций с этими геттерами и сеттерами, пока остановился на варианте поля экспортируемые (как ДТО), но есть и методы API у них, как у вас в примере.
В геттерах проблем не вижу — объявляю только необходимые, а не на все поля, но не припомню случая, когда возникала необходимость в сеттерах, т.к. обычно есть функция:
type Model struct {
filedA string
fieldB string
}
func NewModel(fieldA, fieldB) (Model, error) {...}
Если же сущность требует слишком много данных, я использую зеркальную DTO-структуру:
type ModelDTO struct {
FiledA string
FieldB string
// портянка других полей и вложенных типов
}
type Model struct {
filedA string
fieldB string
// портянка других полей и вложенных типов
}
func NewModel(dto ModelDTO) (Model, error) {...}
В итоге поля модели остаются приватными, а дальнейшая работа происходит исключительно через её API.
Если под "разными usecase" подразумеваются разные методы в слое usecase одного микросервиса - то нет.
Да, я имел в виду различные usecase в рамках одного сервиса. Например, в пакете domain могут быть описаны правила возврата товаров, и несколько usecase могут обращаться к этой бизнес-логике, чтобы избежать ее дублирования у себя. Кроме того, в слое domain я практически всегда размещаю статусы сущностей, которые регламентируют их жизненный цикл и другие глобальные для бизнес-логики типы.
Я много проектов в таком стиле написал, ни разу никаких проблем такие жирные интерфейсы в пакетах бизнес-логики и репо не создали. Можете уточнить, о каких потенциальных проблемах речь?
На своей практике я встречал service с вот такими интерфейсами, которые я называю «жирными»:
type IBasket interface {
AddProductToBasket(...) error
GetProductsFromBasket(...) ([]domain.Product, error)
DeleteProductsFromBasket(...) error
// и еще портяночка методов
}
type IOrder interface {
GetOrder(...) (domain.Order, error)
CreateOrder(...) error
CancelOrder(...) error
RefundOrder(...) error
// и еще портяночка методов
}
type IUser interface {
GetUser(...) (domain.User, error)
UpdateUser(...) error
// и еще портяночка методов
}
type OrderService struct {
basket IBasket
order IOrder
user IUser
}
Со временем функциональность расширяется, service «разрастаются», а вместе с ними и интерфейсы, т.е. растёт сложность системы.
В рамках рефакторинга я старался дробить такие сервисы с «жирными» интерфейсами на изолированные usecase, каждый из которых отвечал за конкретное действие. То есть один большой сервис разбивался на N-ое количество usecase. Руководствуясь принципом разделения интерфейсов для описания зависимостей, в большинстве случаев удавалось естественным образом следовать правилу «один интерфейс — один метод». Подчеркну, что это не обязательная история, но такой подход мне однозначно импонирует.
Благодарю за конструктивный и развернутый комментарий.
И всё же описанный в статье подход однозначно избыточно книжный/формальный. На практике можно и нужно срезать некоторые углы, которые хоть формально и нарушают книжные рекомендации, но при этом не создают проблем в реальных проектах и при этом заметно упрощают работу.
Согласен, что слепое следование любым подходам и принципам — плохая идея. Я постарался отметить это в заключении статьи. Что касается книжности и формальности, то формат и объем статьи ограничены, поэтому не всегда удаётся подробно разобрать все детали и возможные компромиссы. Часть практических аспектов я осознанно вынес в раздел «рекомендации», чтобы дать читателям больше гибкости в применении.
Слои usecase и domain в большинстве проектов нет смысла разделять.
Отчасти согласен, возможна ситуация, когда доменная сущность может быть перенесена в пакет конкретного usecase — как в виде сущности, так и через перенос логики в сам usecase. Однако, если потребуется переиспользовать логику в разных usecase, она вынужденно вернётся в слой domain. С моей точки зрения, заранее закладывать разделение domain/usecase — это выгодный подход, хотя понимаю, что кому-то он может казаться формальным.
Учитывая автоматическую генерацию моков зачастую удобнее сделать один "жирный" интерфейс с десятком-двумя методов чем делать два десятка интерфейсов по одному методу.
В этом вопросе мы с вами расходимся, но я не претендую на бескомпромиссность подхода. В данном контексте я придерживаюсь принципа разделения интерфейсов, чтобы делать их более лёгкими. «Жирный» интерфейс может привести к проблемам при его реализации, что, в свою очередь, нарушает принцип единственной ответственности. Другое дело, если хочется описать интерфейс UserGetter с 2–3 методами для получения пользователя — это вполне валидный кейс, который я считаю приемлемым.
Также не вижу проблемы в множестве мелких интерфейсов с точки зрения читабельности. Открывая конкретный usecase, сразу видно, от каких интерфейсов он зависит. К тому же их названия дают понять, о чём идёт речь.
Транзакции действительно должны быть полностью внутри конкретного метода репозитория, но при этом нет острой необходимости обязательно сохранять связь "один метод репо - одна модель"
В этом тезисе я с вами согласен. В некоторых случаях острая необходимость в агрегате может отсутствовать, и тогда его можно заменить передачей нескольких параметров — это уже вопрос вкуса при проектировании интерфейса репозитория. Однако, с точки зрения бизнес-логики агрегат — не просто контейнер для сущностей. Он подразумевает наличие поведения, затрагивающего дочерние сущности. Например, отмена заказа — это действие, которое влияет не только на сам заказ, но и на товары внутри него через единый метод агрегата. По моему мнению, в этом случае разбиение лишь усложнит интерфейс и излишне раскроет детали реализации.
Что до передачи функции бизнес-логики параметром в метод репозитория - это должно быть не базовой рекомендацией, а редчайшим исключением. Потому что эта функция бизнес-логики вполне может начать делать вызовы в другие сервисы и даже обратно в репозиторий, что, очевидно, создаст кучу проблем.
Согласен, контроль над выполняемыми операциями внутри функции в любом случае необходим, без этого не обойтись.
Вместо использования такого подхода лучше использовать специализированные методы репо.
Согласен, если есть возможность построить взаимодействие с репозиторием через специализированное и простое API без передачи функций, следует идти этим путём — этому стоило бы уделить больше внимания в статье.
Хочу отметить, что в статье я пытался противопоставлять агрегаты (агрегаты + функции) как альтернативу менеджеру транзакций, а не default подход. В статье были рассмотрены два крайних случая: когда нужно просто выполнить операцию атомарно и когда важно знать текущее состояние ресурса, выполнять дополнительные проверки, обрабатывать бизнес-ошибки, а также обеспечивать транзакционность — то есть в тех ситуациях, когда возникает острая необходимость введения менеджера транзакций.
Я полностью согласен с вашим тезисом о том, что на уровне usecase возможна абстракция, описывающая именно «бизнес-транзакцию». Это не является «утечкой». Важный нюанс — определение необходимых гарантий и свойств этих «бизнес-транзакций». Предполагаю, что реализация паттерна saga примерно так и выглядит.
Подсвечу, что в статье я говорю о случаях, когда разработчики осознанно или неосознанно «сверлят дырочку», используя TransactionManager, через который «утекает» именно ACID-транзакция. Это создает жесткую связь кодовой базы и конкретной технологии, а также приводит к некорректному использованию свойств ACID-транзакций со всеми вытекающими последствиями.
так, все же протечка абстракции - это когда вы пользуетесь специфической возможностью реализации интерфейса, минуя абстракцию.
Выглядит так, что наши рассуждения не противоречат друг другу.
Поясню:
Если репозиторий является абстракцией, предоставляющей интерфейс для работы с хранилищем и скрывающей его конкретную природу (будь то РСУБД, NoSQL или текстовый файл), то раскрытие в этом интерфейсе информации о «транзакциях» как раз означает использование специфической возможности конкретной реализации. Например, реляционные БД поддерживают ACID-транзакции, а текстовые файлы — нет. В этом контексте это и является «утечкой абстракции».
Если же у вас есть бизнес-требование - совершать атомарно несколько операций с хранилищем, то вы вводите абстракцию, скажем, OneShotExecutor и реализуете ее поверх, скажем, бд-транзакции. Ура, у нас нет протечки.
Здесь я тоже не вижу противоречий. Возможно, дело в терминологии: агрегат и интерфейс взаимодействия с ним выполняют ту же роль, что и OneShotExecutor в вашей терминологии — отвечают за атомарность операций и предотвращают «утечку абстракции».
Все верно, с точки зрения usecase интерфейс для работы с агрегатом «order + products» и интерфейс для работы с агрегатом «order + client» — это разные интерфейсы.
Вопрос переиспользования кодовой базы репозитория, который реализует эти интерфейсы, решается через приватные методы репозитория.
На мой взгляд, с переиспользованием приватных методов репозитория, содержащих как raw SQL, так и ORM-модели, нужно быть аккуратным. Это особенно важно в контексте обновления данных, о чём я упоминаю в статье.
предлагается на каждую комбинацию репозиториев заводить агрегат и хардкодить его сохранение?
В рассматриваемом фрагменте статьи предлагается отказаться от введения абстракции менеджера транзакций, чтобы избежать «утечки» ACID-транзакции на уровень usecase через интерфейс репозитория. Вместо этого рекомендуется использовать агрегаты и проектировать соответствующие интерфейсы, что, в свою очередь, побуждает разработчика отходить от подхода «один репозиторий — одна таблица».
Надеюсь, я ответил на ваш вопрос. Если нет, пожалуйста, уточните вопрос :)
Не покидает ощущение попытки сделать из того, что вполне себе может оставаться простым - сложным, "по книжкам, как деды завещали"
Старался не использовать заезженные иллюстрации со слоями, но и выдумывать свою терминологию не решился. Заранее подозревал, что после прочтения статьи может показаться, будто у меня на столе лежит жёлтая книжечка Мартина вместо Библии, но это не так :)
Возможно, ощущение ошибочное, но если перед всеми другими призмами смотреть на код через призму KIS(s) - он заиграет совсем другими красками (но это не точно)
По моему мнению, описанный подход и рекомендации идеологически соонаправлены с принципом KISS, так как конечная цель — снижение сложности. В частности, это достигается за счёт изоляции бизнес-логики, отказа от перегруженных service в пользу простых usecase и уменьшения связанности системы.
Согласен, что выстраивание архитектуры — это не бесплатный процесс, но на личном опыте убедился: в продуктовых командах, работающих с бизнесом, рано или поздно встаёт вопрос о поддерживаемости и масштабировании системы.
У меня не было опыта работы в инфраструктурных командах, но предполагаю, что для "строгих" инженерных задач все эти слои и абстракции действительно могут оказаться избыточными.
Менеджер транзакций — это абстракция, которая позволяет работать с ACID-транзакциями вне репозитория. В данной статье рассматривается как антипаттерн, поскольку может привести к проблемам, о которых идёт речь в тексте статьи.
Скажите, го не имеет смысла использовать без микросервисов? Они глубоко в паттернах гошки
В статье не рассматривается выбор между микросервисной и монолитной архитектурами, так как это более высокоуровневый вопрос. Основное внимание уделяется построению архитектуры самого сервиса на уровне кодовой базы: слои, компоненты и их взаимодействие.
Если говорить вне контекста статьи, то Go можно использовать как для монолитов, так и для микросервисов. Выбор архитектурного стиля зависит не от языка программирования, а от требований к разделению зон ответственности.
Согласен, но разрабатывать вполне пристойные монолиты, не превращая их в запущенную заводь — это выгодная стратегия с самого начала.
Думаю, что это вопрос популяризации в команде необходимых подходов и поиска компромиссов. При понятной доменной области и адекватных сроках не составляет труда сразу выделить пакеты для user, order и basket, описывать в каждом из них изолированные usecase, которые используют понятные и «легкие» интерфейсы.
Такой монолит будет by design модульным и его будет приятно распиливать на сервисы. А если не доживём до распила, то хотя бы позывов писать статьи не будет :)
Благодарю за конструктивный и развернутый комментарий.
Уточню, чтобы избежать недопонимания. Я не хотел в статье прямо называть это антипаттерном, чтобы не вызывать ненужной эмоциональной реакции у читателей, так как понимаю, что менеджер транзакций — это скорее стандартный подход, которым пользуются разработчики. В статье я хотел подчеркнуть возможные последствия его использования, с которыми сталкивался на личном опыте, а также описать альтернативу и объяснить, почему считаю ее более корректной. А вот в комментариях, да, я сделал такое утверждение.
Я согласен с тезисом о том, что на уровне usecase возможна абстракция, описывающая именно «бизнес-транзакцию», и, соответственно, сущность менеджера для них. Важный нюанс — определение необходимых гарантий и свойств этих «бизнес-транзакций», а также их реализация.
Я не согласен с тем, что на уровень usecase корректно пробрасывать ACID-транзакцию и менеджер для ACID-транзакций — об этом я и пишу в статье. Это создает жесткую связь между конкретной технологией и кодовой базой, усложняет управление транзакциями и дает возможность использовать ACID-транзакции некорректно, то есть не так, как изначально предполагали разработчики СУБД.
В этом контексте речь идет как раз о «бизнес-транзакциях», т.е. о сагах, которые упомянул @powerman.
С моей точки зрения, если у вас есть необходимость в сагах, то менеджер саг будет находиться слоем выше и, скорее всего, оперировать usecase как этапами выполнения саги. В этом случае ACID-транзакция также остается деталью реализации репозитория и не проникает на уровень usecase.
Благодарю за конструктивный и развернутый комментарий.
Действительно, интерфейс публичный, но речь скорее о том, что не стоит экспортировать чужие интерфейсы из соседних usecase. Возможно, формулировка была неудачной, но именно это я и имел в виду.
Хм, спасибо за замечание. Возможно, проблема в расхождении терминологии. В статье я старался раскрыть термин «репозиторий» и требования, которые к нему предъявляются.
В геттерах проблем не вижу — объявляю только необходимые, а не на все поля, но не припомню случая, когда возникала необходимость в сеттерах, т.к. обычно есть функция:
Если же сущность требует слишком много данных, я использую зеркальную DTO-структуру:
В итоге поля модели остаются приватными, а дальнейшая работа происходит исключительно через её API.
Спасибо, что заметили опечатку. Действительно, там должна быть папка internal без "s". Поправлю.
Да, я имел в виду различные usecase в рамках одного сервиса. Например, в пакете domain могут быть описаны правила возврата товаров, и несколько usecase могут обращаться к этой бизнес-логике, чтобы избежать ее дублирования у себя. Кроме того, в слое domain я практически всегда размещаю статусы сущностей, которые регламентируют их жизненный цикл и другие глобальные для бизнес-логики типы.
На своей практике я встречал service с вот такими интерфейсами, которые я называю «жирными»:
Со временем функциональность расширяется, service «разрастаются», а вместе с ними и интерфейсы, т.е. растёт сложность системы.
В рамках рефакторинга я старался дробить такие сервисы с «жирными» интерфейсами на изолированные usecase, каждый из которых отвечал за конкретное действие. То есть один большой сервис разбивался на N-ое количество usecase. Руководствуясь принципом разделения интерфейсов для описания зависимостей, в большинстве случаев удавалось естественным образом следовать правилу «один интерфейс — один метод». Подчеркну, что это не обязательная история, но такой подход мне однозначно импонирует.
Благодарю за конструктивный и развернутый комментарий.
Согласен, что слепое следование любым подходам и принципам — плохая идея. Я постарался отметить это в заключении статьи. Что касается книжности и формальности, то формат и объем статьи ограничены, поэтому не всегда удаётся подробно разобрать все детали и возможные компромиссы. Часть практических аспектов я осознанно вынес в раздел «рекомендации», чтобы дать читателям больше гибкости в применении.
Отчасти согласен, возможна ситуация, когда доменная сущность может быть перенесена в пакет конкретного usecase — как в виде сущности, так и через перенос логики в сам usecase. Однако, если потребуется переиспользовать логику в разных usecase, она вынужденно вернётся в слой domain. С моей точки зрения, заранее закладывать разделение domain/usecase — это выгодный подход, хотя понимаю, что кому-то он может казаться формальным.
В этом вопросе мы с вами расходимся, но я не претендую на бескомпромиссность подхода. В данном контексте я придерживаюсь принципа разделения интерфейсов, чтобы делать их более лёгкими. «Жирный» интерфейс может привести к проблемам при его реализации, что, в свою очередь, нарушает принцип единственной ответственности. Другое дело, если хочется описать интерфейс UserGetter с 2–3 методами для получения пользователя — это вполне валидный кейс, который я считаю приемлемым.
Также не вижу проблемы в множестве мелких интерфейсов с точки зрения читабельности. Открывая конкретный usecase, сразу видно, от каких интерфейсов он зависит. К тому же их названия дают понять, о чём идёт речь.
В этом тезисе я с вами согласен. В некоторых случаях острая необходимость в агрегате может отсутствовать, и тогда его можно заменить передачей нескольких параметров — это уже вопрос вкуса при проектировании интерфейса репозитория. Однако, с точки зрения бизнес-логики агрегат — не просто контейнер для сущностей. Он подразумевает наличие поведения, затрагивающего дочерние сущности. Например, отмена заказа — это действие, которое влияет не только на сам заказ, но и на товары внутри него через единый метод агрегата. По моему мнению, в этом случае разбиение лишь усложнит интерфейс и излишне раскроет детали реализации.
Согласен, контроль над выполняемыми операциями внутри функции в любом случае необходим, без этого не обойтись.
Согласен, если есть возможность построить взаимодействие с репозиторием через специализированное и простое API без передачи функций, следует идти этим путём — этому стоило бы уделить больше внимания в статье.
Хочу отметить, что в статье я пытался противопоставлять агрегаты (агрегаты + функции) как альтернативу менеджеру транзакций, а не default подход. В статье были рассмотрены два крайних случая: когда нужно просто выполнить операцию атомарно и когда важно знать текущее состояние ресурса, выполнять дополнительные проверки, обрабатывать бизнес-ошибки, а также обеспечивать транзакционность — то есть в тех ситуациях, когда возникает острая необходимость введения менеджера транзакций.
Я полностью согласен с вашим тезисом о том, что на уровне usecase возможна абстракция, описывающая именно «бизнес-транзакцию». Это не является «утечкой». Важный нюанс — определение необходимых гарантий и свойств этих «бизнес-транзакций». Предполагаю, что реализация паттерна saga примерно так и выглядит.
Подсвечу, что в статье я говорю о случаях, когда разработчики осознанно или неосознанно «сверлят дырочку», используя TransactionManager, через который «утекает» именно ACID-транзакция. Это создает жесткую связь кодовой базы и конкретной технологии, а также приводит к некорректному использованию свойств ACID-транзакций со всеми вытекающими последствиями.
Выглядит так, что наши рассуждения не противоречат друг другу.
Поясню:
Если репозиторий является абстракцией, предоставляющей интерфейс для работы с хранилищем и скрывающей его конкретную природу (будь то РСУБД, NoSQL или текстовый файл), то раскрытие в этом интерфейсе информации о «транзакциях» как раз означает использование специфической возможности конкретной реализации. Например, реляционные БД поддерживают ACID-транзакции, а текстовые файлы — нет. В этом контексте это и является «утечкой абстракции».
Здесь я тоже не вижу противоречий. Возможно, дело в терминологии: агрегат и интерфейс взаимодействия с ним выполняют ту же роль, что и OneShotExecutor в вашей терминологии — отвечают за атомарность операций и предотвращают «утечку абстракции».
Все верно, с точки зрения usecase интерфейс для работы с агрегатом «order + products» и интерфейс для работы с агрегатом «order + client» — это разные интерфейсы.
Вопрос переиспользования кодовой базы репозитория, который реализует эти интерфейсы, решается через приватные методы репозитория.
На мой взгляд, с переиспользованием приватных методов репозитория, содержащих как raw SQL, так и ORM-модели, нужно быть аккуратным. Это особенно важно в контексте обновления данных, о чём я упоминаю в статье.
В рассматриваемом фрагменте статьи предлагается отказаться от введения абстракции менеджера транзакций, чтобы избежать «утечки» ACID-транзакции на уровень usecase через интерфейс репозитория. Вместо этого рекомендуется использовать агрегаты и проектировать соответствующие интерфейсы, что, в свою очередь, побуждает разработчика отходить от подхода «один репозиторий — одна таблица».
Надеюсь, я ответил на ваш вопрос. Если нет, пожалуйста, уточните вопрос :)
Старался не использовать заезженные иллюстрации со слоями, но и выдумывать свою терминологию не решился. Заранее подозревал, что после прочтения статьи может показаться, будто у меня на столе лежит жёлтая книжечка Мартина вместо Библии, но это не так :)
По моему мнению, описанный подход и рекомендации идеологически соонаправлены с принципом KISS, так как конечная цель — снижение сложности. В частности, это достигается за счёт изоляции бизнес-логики, отказа от перегруженных service в пользу простых usecase и уменьшения связанности системы.
Согласен, что выстраивание архитектуры — это не бесплатный процесс, но на личном опыте убедился: в продуктовых командах, работающих с бизнесом, рано или поздно встаёт вопрос о поддерживаемости и масштабировании системы.
У меня не было опыта работы в инфраструктурных командах, но предполагаю, что для "строгих" инженерных задач все эти слои и абстракции действительно могут оказаться избыточными.
Менеджер транзакций — это абстракция, которая позволяет работать с ACID-транзакциями вне репозитория. В данной статье рассматривается как антипаттерн, поскольку может привести к проблемам, о которых идёт речь в тексте статьи.
В статье не рассматривается выбор между микросервисной и монолитной архитектурами, так как это более высокоуровневый вопрос. Основное внимание уделяется построению архитектуры самого сервиса на уровне кодовой базы: слои, компоненты и их взаимодействие.
Если говорить вне контекста статьи, то Go можно использовать как для монолитов, так и для микросервисов. Выбор архитектурного стиля зависит не от языка программирования, а от требований к разделению зон ответственности.
Тоже заметил, что позиция автора в обсуждении непоследовательна.
В итоге, выглядит так, что спонсорства от комьюнити на фулл-тайм разраба может не хватить, а проекту от этого лучше не станет, да.
Судя по issue, эпопея так и не завершилась и автор по-прежнему остаётся мейнтейнером?
Правильно ли я понимаю, что форк с вашей стороны произошёл из-за медленного ревью PR?