Comments 26
Совсем не нравится как вы здесь представляете репозитории.

1. Нейминг

SessionRepo.SaveSession, SessionRepo.DeleteSession, UserRepo.CreateUser и тд

Зачем нужен постфикс у методов? Это бессмысленное дублирование, у вас же не может быть метода SessionRepo.SaveUser…

2. Мне кажется методы в целом несогласованны:

UserRepo.CreateUser — очевидно Create не является зоной ответственности репозитория.
SaveSession(context.Context, UserID, TokenID, Origin) — из имени метода следует что вы сохраняете сессию а не UserID, TokenID, Origin.

И далее, мне видится что для вас репозиторий это абстракция над бд в вакууме, тогда как, кроме этого, интерфейс репозитория должен быть схож с интерфейсом коллекций. Если следовать этому определению методы типа UpdatePassword(context.Context, UserID, []byte) отпадут сами собой.

Вы пишите «Мы можем тестировать всю бизнес-логику абстрагируясь от БД, сервера, протокола», но при этом в коде я вижу как http handler напрямую запросы в бд делает. Т.е. если вы вдруг решите перейти на grpc, то handler придется переписывать.
И второе, что бросилось в глаза, не забывайте возвращать конекшены обратно в пул: defer conn.Close(),
в этом месте:
for rows.Next() {
if err != nil {
return
}
...
}

потенциально можете исчерпать все конекшены в базу.
Благодарю, там пропущен кусок текста, объясняющий, что так нельзя делать :)

На мой взгляд тут вообще не пахнет чистой архитектурой.
Вы в репозиторий при создании юзера передаете какой то TaskNotification — как это относится к созданию юзера в персистент слое???


Функция func (a *Application) CreateUser зачем то делает еще Login, как это узнать не глядя в код и самое главное зачем вы смешиваете разные действия в одно? Что вы будете делать когда создали юзера а логин по какой то причине упал?
Почему (*User, AuthToken, error) возвращает юзера по ссылке а остальные данные по значению? Так же в CreateUser в репозиторий вы этого юзера передаете по значению.


У вас User публичная структура и ничего не мешает взять ее и создавать с частичными полями. А по правильному архитектура не должна давать создавать невалидные объекты.


Дальше устал писать и разбирать.

Вопросы разумные.
Только вот отношения к clean architecture, по-моему, не имеют.
Основной посыл доклада/статьи — бить все на слои и тогда замена реализации CreateUser (при смене MySQL на NoSQL, например) пройдет менее болезненно.

Чистая архитектура это не мы как то разбили криво на слои. Не может быть в чистой архитектуре в слое который сохраняет юзера в базу какого то TaskNotification
или же создание бизнес сущности User не может быть просто структура в которую можно накидать как угодно какие угодно данные.
Те если бы статья называлась пример наших костылей, то я и слова бы не сказал. А так замахнулись на чистую, понимаете ли, нашу архитектуру. И в результате выкатили какую то поделку.

Тем не менее, подход вроде описан плюс-минус верно.
Но я бы мог и Вашу статью на этот счет почитать, когда будет.

Хмм. "Независимость от конкретной БД" разбивается в пух и прах наличием RETURNING в INSERT.

Ну например получение значения последнего вставленного ID отдельным запросом может произойти с ошибкой, это разве не изменит логику создания нового юзера и последующую обработку ошибок?
Это произойдет в этом же методе, тогда бизнес логика не узнает никак про то, что там returning или отдельным запросом происходит.
Только тогда придется все методы, где есть эти создания новых записей, переписать. Я понимаю, что использовать RETURNING удобно, это меньше кода, меньше движений и проверок, но если мы говорим про «чистую архитектуру», это явная завязка на конкретную реализацию SQL в паре конкретных RDB, разве нет?
Нет, там может быть mongo, текстовый файл на диске и т.д.
На уровень бизнес логики это, опять таки, совершенно никак не влияет.

Ты не будешь менять бизнес логику при такой системе, даже если заменишь pg на mongo.
Ты не будешь менять бизнес логику при такой системе, даже если заменишь pg на mongo.
А стоило бы.

Инструмент выбирают под задачу. Если инструмент срочно потребовалось изменить, значит, задача поменялась. А делать возможность в любой момент поменять tcp на почтовых голубей просто ради мнимой универсальности — такое себе.
Нет, не стоило бы, повторюсь, пожалуй

Если и нужно менять инструмент, значит измени инструмент. Eсли изменилась бизнес логика, меняй бизнес логику, БД лишь деталь и я прикладные задачи нашего проекта в 99% случаев не зависят от БД.
Но в любом случае, нужно понимать, что «чистая архитектура» это не свод законов, за нарушение которых тебя накажут, любой из нас может все вывалить в один файл, разница в том, что страдать будет поддерживающий.
Вы писали в прошлом сообщении о замене «pg на mongo». Это совершенно разные базы данных, они нужны для совершенно разных задач. Если перед вами действительно стоит такая проблема, то:
1. либо изменилась сама задача
2. либо изначально ошиблись при проектировании
3. либо старый архитектор уволился, и ему на смену пришел некомпетентный человек, который руководствуется в первую очередь хайпом

Абстрации текут. Если при проектировании закладывать возможность в любой момент перепрыгнуть с постгреса на монгу, то реализация не будет эффективной. Некоторая бизнес-логика может быть просто нереализуема в принципе, либо реализация сопряжена с такими проблемами, что нужно все переписывать от начала до конца.

4) На момент разработки приложения использование Монги было бы авантюрой, ResumeDrivenDevelopment или её вообще не было**

Вы не правы. Если вы делаете нормальные слои и взаимодействие то заменить какую то имплементацию будет относительно просто. Говорю из своего опыта когда приходилось мигрировать проекты с Монги или ДинамоДБ на Постгрес.

Боюсь, что вы не до конца поняли мою мысль. С динамо не сталкивался, но в монге нет ничего, чего бы не было в постгресе. Поэтому миграция тут достаточно тривиальная.

Возьмите обратную ситуацию — когда в типичном запросе используются несколько джоинов, и вы перенесли это на мап-редус, просев в производительности на порядок. Либо начали делать денормализацию и столкнулись с неактуальными данными.

Обобщая: перейти с текстовых файлов на бд — проблем нет, просто заменить «адаптер». Перейти с бд на файлы — проблем не оберешься.
Спасибо за статью, идея раскрыта, примеры простые, всё понятно. Ясное дело что они не рабочие, а абстрактные, но критика по делу, поддерживаю.
Возможно кто-то подскажет где посмотреть реальные примеры с луковой архитектурой на go?
<портянка кода с четырьмя if err != nil и возвратом 3 значений>
Это простой императивный код, который легко читается и легко поддерживается.

Хорошая шутка

Сторонники Го не поймут эту шутку. Они действительно считают что так и надо писать.

Лена, спасибо за статью. Мне как раз предстоит рефакторинг сервиса с подобными проблемами, и я думаю, как лучше за это взяться. Теперь есть хорошее понимание

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

Only those users with full accounts are able to leave comments. Log in, please.
Information
Founded

1 February 2008

Location

Россия

Employees

11–30 employees

Registered

24 February 2010

Habr blog