Как стать автором
Обновить

Комментарии 27

Вашему предложению написать статью про чистую архитектуру для приложений на Go однозначно да, было бы лично мне интересно
Ок, значит ждите go-специфики :)
Очень интересует данная тема.
Никак не могу сообразить, как решить одну задачку в рамках правильной архитектуры. Есть допустим в проекте пользователи и две такие сущности: «Друзья пользователя» и «Кто онлайн». Отдельно работают замечательно, ничего друг про друга не знают. Хранятся и те и другие данные в mysql. Но если надо вывести тех друзей, которые сейчас онлайн — самое простое и быстрое решение это сделать выборку по двум таблицам (friends join users_online). Но её сделать нельзя, ибо «Друзья» ничего не знают про формат хранения онлайн-юзеров, онлайн-юзеры не знают про формат хранения друзей, а все остальные не знают ни того ни другого. Как быть?
Разве «друзья пользователя» — это сущность? По-моему, это коллекция объектов «Пользователь». Как и «Кто онлайн», кстати. Даже если у вас друзья имеют какие-то дополнительные атрибуты, это все равно только характеристика связи. Или нет?
Да, это не сущность в плане «Модель», а скорее некий сервис. В случае друзей — у него есть методы «Отправить запрос на добавление в друзья», «Принять в друзья», «Удалить из друзей», «Получить список друзей». В случае онлайна — «Получить список тех кто онлайн», «Узнать онлайн ли данный юзер» и «Поставить отметку времени (типа пинг = я онлайн)».
«Кто онлайн», по идее, должен быть статусом пользователя, а не отдельной сущностью. А друзья пользователя — это бридж-таблица many-many к юзерам. Ну это если работа идет в рамках единой БД.
Оно примерно так и хранится. Только время последнего визита лежит не в таблице пользователей, а в отдельной. А через год — может будет лежать в Редисе. Формат хранения друзей также может измениться. Поэтому хочу сделать так, чтобы класс Юзер ничего не знал про то, как хранятся его друзья, и как хранится его статус «онлайн», и он мог просто получать эти данные от двух независимых от себя сервисов. А как эти сервисы подружить между собой — вот в чем вопрос. При том что они не должны ничего друг о друге знать? Т.е. одним запрос тут никак не сделаешь, надо выбирать сначала всех друзей, потом проверять их отдельно через сервис «кто онлайн». А можно сэкономить своё время, и захардкодить более быстрое решение через один запрос с join.
ну, ИМХО, это путь «по глубокому внутреннему миру». Всё-таки в микросервисы нужно выделять отдельные бизнес-компоненты и уже их «общать» друг с другом. А тут, получается, вы пилите самого юзера на микросервисы. Имеет ли это смысл? Возможно. Всё зависит от задач.
nelson, а у вас ORM используется, или вы sql запросы сами формируете?
У нас ActiveRecord, который позволяет использовать в том числе запросы забитые вручную. ORM в свое время показался не самыми гибким для развивающегося стартапа… (много раз приходилось менять схемы, и делать это надо было быстро, принцип бережливго стартапа).
Можно написать класс-джойнер. Первый сервис — СписокДрузей — содержит запрос как получать список друзей, СписокОнлайн содержит запрос как получить список пользователей онлайн: «select * from UsersOnline u {0} where u.status = 'online'». Вместо "{0}" может быть пустая строка или подстрока для джойна «join ({0}) something on u.user_id = something.user_id». И запрос и подстрока хранятся в СписокОнлайн. Ну и нужно учесть что where тут должен быть только один. Джойнер должен понимать как сджойнить данные двух сервисов: либо сгенерит на основании двух сервисов sql с джойном, или, если данные одного сервиса в кэше а другого в базе, то возьмёт айдишки из кэша и подставит их в запрос. Но предупрежу: генерация sql путём конкатенации строк — скользкая дорожка, часто чреватая труднообнаруживаемыми ошибками.
Если в приложении не нужно джойнить таблицы пачками, то проще и надежнее будет просто сделать два запроса вместо одного, отфильтровав вторым сервисом результаты первого.
Можно просто захардкодить sql запрос со всеми джойнами, а не вычислять его динамически, но тут возникают проблемы с гибкостью и дублированием знания.
Ну а ещё лучше — переписать слой доступа к данным и не использовать ActiveRecord, но самый лучший путь не всегда самый верный :), т.к. есть ещё сроки, бюджет, возможно жёсткие требования по быстродействию.

Я бы лично сделал так: если проблема единичная, то отфильтровал бы вторым сервисом результаты первого. Если проблема повсеместная — избавился бы от ActiveRecord и использовал бы ORM. Удачи
nelson, расскажете потом что вы выбрали и что в итоге получилось?
Спасибо за ваш развернутый комментарий выше.
Я остановился на варианте, когда время последнего визита становится полем пользователя, а класс для работы с друзьями содержит метод «Получить друзей», которому можно передать параметр «только онлайн», и он будет делать нужный джойн. То есть — такое односторонее нарушение инкапсуляции. Теоретически это плохо — если изза растущей нагрузки мне придётся хранить список онлайн юзеров в памяти — придётся переписывать и класс работы с друзьями. Надеюсь, такое время наступит не скоро.
Как вариант, если будет высокая нагрузка, можно специально для Users_online завести отдельную базу данных на отдельном сервере и настроить репликацию в неё таблички Users из основной базы. Но это нужно по ситуации смотреть, что и как лучше оптимизировать.
ActiveRecord по идее одна из разновидностей ORM
Я подразумевал Repository, когда работа с данными из бд идёт почти так же, как если бы они были массивами в оперативной памяти.
Ну а вообще ActiveRecord может быть выстроена поверх простой Mini-ORM наподобие Dapper-а, тогда Dapper будет непосредственно маппить, а ActiveRecord будет слоем доступа к данным, который будет скармливать sql-ные запросы непосредственно ORM. Ну а вообще я пожалуй пропущу этот назревающий холивар по поводу терминологии.
Можно сделать сущность «Друзья Онлайн» или сделать у «Друзей» характеристику «Онлайн», в модели все будет логично а как это вытаскивается из БД это уже вопрос инфраструктуры
По идее тут должен быть один набор сущностей «пользователь» с булевым членом «онлайн» и набором ссылок «друзья» на некоторых пользователей из основного набора. И фильтр, позволяющий извлечь из набора пользователей, только пользователей с установленным членом «онлайн». Это, максимум, два внутренних круга на диаграмме (если фильтр отнести к сценариям). А как их заполнять эффективно — это дело внешних кругов, которые не просто могут, а должны знать публичные, как минимум, интерфейсы внутренних кругов.
В одном из последних проектов пришел именно к такой архитектуре, но там еще основывался на DDD. Надо прочесть первоисточник. Спасибо!
Скорее, чтобы было понятно широким массам. Роберт Мартин, не настолько нуждается в своих изобретениях, мягко говоря.
Собственно и DDD это классические приемы (новое там только UL), названные DDD.
Проблема в том, что идея выносить бизнес-логику из sql при серьёзных нагрузках может приводить к проблемам с производительностью решения.
Бизнес-логика — это идея того, что должно быть сделано, а язык и уровень стека, на котором эта логика будет реализована уже зависит от требований.
Чем выше абстракция — тем хуже перфоманс, это очевидно :-).
Обычно людям приходит в голову идея вносить бизнес-логику в sql, когда встречаются с проблемами производительности, а не наоборот. С другой стороны, для sql применимі те же архитектурные правила —
Ну вот я примерно про то же — архитектура и язык реализации — это ортогональные сущности.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.