Comments 27
Вашему предложению написать статью про чистую архитектуру для приложений на Go однозначно да, было бы лично мне интересно
Очень интересует данная тема.
Никак не могу сообразить, как решить одну задачку в рамках правильной архитектуры. Есть допустим в проекте пользователи и две такие сущности: «Друзья пользователя» и «Кто онлайн». Отдельно работают замечательно, ничего друг про друга не знают. Хранятся и те и другие данные в mysql. Но если надо вывести тех друзей, которые сейчас онлайн — самое простое и быстрое решение это сделать выборку по двум таблицам (friends join users_online). Но её сделать нельзя, ибо «Друзья» ничего не знают про формат хранения онлайн-юзеров, онлайн-юзеры не знают про формат хранения друзей, а все остальные не знают ни того ни другого. Как быть?
Никак не могу сообразить, как решить одну задачку в рамках правильной архитектуры. Есть допустим в проекте пользователи и две такие сущности: «Друзья пользователя» и «Кто онлайн». Отдельно работают замечательно, ничего друг про друга не знают. Хранятся и те и другие данные в 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. Удачи
Если в приложении не нужно джойнить таблицы пачками, то проще и надежнее будет просто сделать два запроса вместо одного, отфильтровав вторым сервисом результаты первого.
Можно просто захардкодить sql запрос со всеми джойнами, а не вычислять его динамически, но тут возникают проблемы с гибкостью и дублированием знания.
Ну а ещё лучше — переписать слой доступа к данным и не использовать ActiveRecord, но самый лучший путь не всегда самый верный :), т.к. есть ещё сроки, бюджет, возможно жёсткие требования по быстродействию.
Я бы лично сделал так: если проблема единичная, то отфильтровал бы вторым сервисом результаты первого. Если проблема повсеместная — избавился бы от ActiveRecord и использовал бы ORM. Удачи
nelson, расскажете потом что вы выбрали и что в итоге получилось?
Спасибо за ваш развернутый комментарий выше.
Я остановился на варианте, когда время последнего визита становится полем пользователя, а класс для работы с друзьями содержит метод «Получить друзей», которому можно передать параметр «только онлайн», и он будет делать нужный джойн. То есть — такое односторонее нарушение инкапсуляции. Теоретически это плохо — если изза растущей нагрузки мне придётся хранить список онлайн юзеров в памяти — придётся переписывать и класс работы с друзьями. Надеюсь, такое время наступит не скоро.
Я остановился на варианте, когда время последнего визита становится полем пользователя, а класс для работы с друзьями содержит метод «Получить друзей», которому можно передать параметр «только онлайн», и он будет делать нужный джойн. То есть — такое односторонее нарушение инкапсуляции. Теоретически это плохо — если изза растущей нагрузки мне придётся хранить список онлайн юзеров в памяти — придётся переписывать и класс работы с друзьями. Надеюсь, такое время наступит не скоро.
ActiveRecord по идее одна из разновидностей ORM
Я подразумевал Repository, когда работа с данными из бд идёт почти так же, как если бы они были массивами в оперативной памяти.
Ну а вообще ActiveRecord может быть выстроена поверх простой Mini-ORM наподобие Dapper-а, тогда Dapper будет непосредственно маппить, а ActiveRecord будет слоем доступа к данным, который будет скармливать sql-ные запросы непосредственно ORM. Ну а вообще я пожалуй пропущу этот назревающий холивар по поводу терминологии.
Ну а вообще ActiveRecord может быть выстроена поверх простой Mini-ORM наподобие Dapper-а, тогда Dapper будет непосредственно маппить, а ActiveRecord будет слоем доступа к данным, который будет скармливать sql-ные запросы непосредственно ORM. Ну а вообще я пожалуй пропущу этот назревающий холивар по поводу терминологии.
Можно сделать сущность «Друзья Онлайн» или сделать у «Друзей» характеристику «Онлайн», в модели все будет логично а как это вытаскивается из БД это уже вопрос инфраструктуры
По идее тут должен быть один набор сущностей «пользователь» с булевым членом «онлайн» и набором ссылок «друзья» на некоторых пользователей из основного набора. И фильтр, позволяющий извлечь из набора пользователей, только пользователей с установленным членом «онлайн». Это, максимум, два внутренних круга на диаграмме (если фильтр отнести к сценариям). А как их заполнять эффективно — это дело внешних кругов, которые не просто могут, а должны знать публичные, как минимум, интерфейсы внутренних кругов.
В одном из последних проектов пришел именно к такой архитектуре, но там еще основывался на DDD. Надо прочесть первоисточник. Спасибо!
Какой-то обычный классический DDD названный другими словами (видимо дабы выдать за своё изобретение).
Проблема в том, что идея выносить бизнес-логику из sql при серьёзных нагрузках может приводить к проблемам с производительностью решения.
Бизнес-логика — это идея того, что должно быть сделано, а язык и уровень стека, на котором эта логика будет реализована уже зависит от требований.
Бизнес-логика — это идея того, что должно быть сделано, а язык и уровень стека, на котором эта логика будет реализована уже зависит от требований.
Чем выше абстракция — тем хуже перфоманс, это очевидно :-).
Обычно людям приходит в голову идея вносить бизнес-логику в sql, когда встречаются с проблемами производительности, а не наоборот. С другой стороны, для sql применимі те же архитектурные правила —
Sign up to leave a comment.
Чистая архитектура