Привет! Это абсолютно синтетический пример. Его смысл в том, чтобы продемонстрировать, что использование клиентов значительно упрощает код и повышает его тестируемость.
В реальной жизни такие куски кода стоит покрывать ретраями. Если же хотим очень высокую надежность, то можно использовать паттерн transactional outbox
В данном случае репозиторий не занимается конвертацией. Он просто возвращает объект(ы) алхимии.
Если переводить на язык слоистой архитектуры, то этот объект алхимии может являться «доменным объектом». Этот объект далее можно использовать в своих сервисах.
В нашем случае сервис, как написано в статье, выполняет бизнес логику. Внутри этого сервиса осуществляется конвертация в нужную для бизнес-сценария схему 1 раз. Если говорить с точки зрения слоистой архитектуры, то сервис и эти схемы являются компонентами одного и того же слоя.
Но как я сказал, слоистой архитектуры в той интерпретации, в которой она часто приводится в книжках (с кружочками и стрелочкой зависимостей, направленной внутрь), у нас нет.
Поэтому и слоев в той интерпретации из книжек у нас тоже нет. Воспринимай слово «слой» в моем ответе как некое множество объектов, которые выполняют свою функцию на определенном этапе обработки бизнес-сценария. Или, если по-простому, на этапе обработки запроса пользователя (упрощенный пример).
Так что в какой-то мере можно сказать, что мы следуем слоистой архитектуре. Но я нарочно не использовал термину оттуда в данной статье, так как полностью мы ей, конечно, не следуем.
Ведь мы не можем определить наш домен так, чтобы он не зависел не от чего. В конечном счете, он зависит от Python.
А у нас нет слоев, так что мы ничего не нарушаем. Шлюзов у нас тоже нет, расскажи мне, о чем ты говоришь здесь? Я не знаю, зачем тут "эта абстракция", я про нее ничего не говорил.
Поясни, пожалуйста, в чем проблема возвращать модели алхимии (по сути дата-маппер) из репозиториев, которые работают с БД? Учитывай, что ненужных слоев и шлюзов здесь нет.
Звучит как некая вкусовщина, ведь мы сами определяем и договариваемся внутри команды, что будут возвращать наши репозитории. Не вижу ничего плохого в том, что они будут возвращать те сущности, с которыми работают (модельки алхимии).
Если нам это надо сконвертить в тот тип, который необходим приложению, то это всегда можно сделать внутри сервиса. Не вижу смысла смешивать репозитории, которые работают с таблицами и бизнес-представление наших сущностей.
Ну так базовый репозиторий не обязывает использовать термины базы данных для построения запросов в бизнес логике. Более того, в бизнес логике запросы как раз строить не стоит. Если нужен какой-нибудь кастомный def find(name: str| None, price: int | None), то его всегда можно написать внутри репозитория.
Рассмотрим пример. В репозитории из advanced-alchemy есть метод, get_one_or_none(...). Как следует из названия, он получает сущность, если нашел ее по фильтрам или возвращает None в ином случае. Я бы не сказал, что это термин базы данных, а в бизнес логике такое использовать крайне удобно.
Ну не соглашусь, переменные окружения никому ничего не должны. Ведь бывают случаи, когда ты хочешь их из .env вытащить, вполне нормальная практика, многие так делают, и ничего криминального в ней нет. И как раз в этом случае придется вызывать dotenv.
По поводу формата енвов уже соглашусь, иметь какой-то более понятный форматы было бы намного лучше.
Но в конечном счете, pydantic_settings позволяют тебе довольно гибко загружать нужные переменные, как из окружения, так и из .env файликов. Обязательно ли всегда использовать именно этот пакет? Нет, конечно, но сам по себе пакет предоставляет много функций и отказываться от него в пользу более простого решения только ради того, чтобы .lock файл сократить, как будто нет смысла.
Тут стоит сказать, наверное, что любая ОРМ приносит за собой какой-то непонятный интерфейс. Намного проще писать на голом SQL, если нам не нравятся кривые интерфейсы. Но тогда мы лишаемся преимуществ, которые приносят ОРМ.
Мне кажется, что подобные решения это всегда какой-то трейдофф. И в данном случае пользы больше, чем неудобств, на мой взгляд.
Не вижу ничего плохого в том, что у нас есть объект, отвечающий за переменные из енвов. Его крайне удобно использовать и мокать в случае чего.
Я про запутанные конфиги и не говорил, в BaseSettings у тебя есть конфиг, в котором можно указать префикс для всех полей в настройках.
Также твой подход через **os.environ удобен в том случае, у тебя основной объект настроек не содержит в себе вложенные настройки. В случае pydantic_settings такие вложенные настройки так же будут подтягиваться из енвов.
Тут, наверное, моя недоработка, стоило показать аннотациями, что репозитории возвращают. Если ты под "доменными объектами" имеешь в виду модельки алхимии, то все так и есть, как ты описал.
По поводу наследования базового репозитория вот, что могу сказать: это строго говоря необязательно, но если ты понимаешь, что есть какие-то методы, которые стоит пошарить несколькими репозиториями, то всегда можно сделать свой базовый репозиторий на основе предоставленного либой. Естественно, эти методы должны работать с теми же объектами, что и базовыый репозиторий. В данном случае это модели алхимии
Стоит ли писать репы с 0? А стоит ли изобратать свой собственный велосипед? Я считаю, что если есть какая-то рабочая штука, закрывающая мои нужды, то нет смысла утруждать себя и писать собственную.
Тогда тебе придется еще и dotenv где-то вызывать. А если тебе надо какие-то префиксы добавить в свои сеттинги? Это нужно в том случае, если у тебя много сервисов, и атрибуты настроек имеют одинаковое название.
В твоем примере тебе придется называть свои переменные по-разному или везде писать алиас, а с использование pydantic-settings достаточно один атрибут в конфиг добавить.
А вытаскивать переменные руками из окружения - довольно неудобная и избыточная практика. Намного проще, когда они вытаскиваются по названию автоматически и лежат сразу в одном месте. У тебя так не возникает вопросов, откуда та или иная переменная, ты сразу знаешь
У нас может быть немного нестандартный подход, так как мы практически не используем .env файлы. Вся разработка и тестирование проходят через docker compose, поэтому необходимые переменные обычно хранятся в самих настройках в виде значений по умолчанию. Если же возникает необходимость запустить сервис вне Compose, можно использовать .env файл, но это скорее исключение, а не рекомендованная практика.
На средах у нас k8s, и там переменные передаются через конфиг мапы, а чувствительные данные — через vault. Далее данные из эти источников попадают в переменные окружения переменными окружения, откуда их уже считывает объект settings.
Привет! У нас нет релизных веток, все релизы происходят через теги. Если что-то где-то поломалось, то мы просто делаем новый merger-request, в котором фиксим проблемы. Как такового fix flow у нас нет. Если надо что-то поправить, мы просто делаем новый исправляющий релиз.
У нас есть две тестовые среды: дев и превью. На дев мы раскатываемся во время разработки, превью при этом не задействуется. Деплой на все тестовые окружения происходит только после создания тега. После чего на превью среде, куда раскатился код под новым тегом, будет произведено тестирование нашего релиза. Ну и если все хорошо - едем на прод.
Привет! Выглядит очень прикольно на самом деле. Единственное чего пока не увидел из коробки - это redis sentinel, а у нас он везде используется. Возможно, это мои личные приколы, но я не очень понимаю, зачем изобретать велосипед и хранить инфру в коде, когда для этого уже есть хорошее и надежное решение в виде композа. Хотя, не отрицаю, что это может быть вполне удобно, надо потестить. В целом, выглядит как довольно хорошее решение для локальной разработки и CI.
Тогда нам очень повезло, что у нас такого нет.) В любом случае, в джобе статического анализа имеется переменная VERSION. С ее помощью ты можешь зафиксировать версию инструмента, если не хочешь, чтобы устанавливалась самая новая версия.
Привет! Мы посчитали, что лучше уж мы нагрузим раннеры и подождем полторы минутки на merge-request'е, чем будем гонять этот mypy только локально. Ведь если гонять его только локально - можно пропустить обновление и не исправить ошибки. Mypy - это тайпчекер и если он сыпет ошибками, то это хорошо, ведь это его основная задача А какое решение здесь видишь ты?
Может, меня сейчас загрызут в комментах, но я считаю, что настройки - это константы, а константы можно просто импортировать в любое нужное место.
Не вижу смысла делать lru_cache, ведь можно просто оставить это в памяти
Привет! Это абсолютно синтетический пример. Его смысл в том, чтобы продемонстрировать, что использование клиентов значительно упрощает код и повышает его тестируемость.
В реальной жизни такие куски кода стоит покрывать ретраями. Если же хотим очень высокую надежность, то можно использовать паттерн transactional outbox
Окей, понял вопрос
В данном случае репозиторий не занимается конвертацией. Он просто возвращает объект(ы) алхимии.
Если переводить на язык слоистой архитектуры, то этот объект алхимии может являться «доменным объектом». Этот объект далее можно использовать в своих сервисах.
В нашем случае сервис, как написано в статье, выполняет бизнес логику. Внутри этого сервиса осуществляется конвертация в нужную для бизнес-сценария схему 1 раз. Если говорить с точки зрения слоистой архитектуры, то сервис и эти схемы являются компонентами одного и того же слоя.
Но как я сказал, слоистой архитектуры в той интерпретации, в которой она часто приводится в книжках (с кружочками и стрелочкой зависимостей, направленной внутрь), у нас нет.
Поэтому и слоев в той интерпретации из книжек у нас тоже нет. Воспринимай слово «слой» в моем ответе как некое множество объектов, которые выполняют свою функцию на определенном этапе обработки бизнес-сценария. Или, если по-простому, на этапе обработки запроса пользователя (упрощенный пример).
Так что в какой-то мере можно сказать, что мы следуем слоистой архитектуре. Но я нарочно не использовал термину оттуда в данной статье, так как полностью мы ей, конечно, не следуем.
Ведь мы не можем определить наш домен так, чтобы он не зависел не от чего. В конечном счете, он зависит от Python.
А у нас нет слоев, так что мы ничего не нарушаем.
Шлюзов у нас тоже нет, расскажи мне, о чем ты говоришь здесь?
Я не знаю, зачем тут "эта абстракция", я про нее ничего не говорил.
Поясни, пожалуйста, в чем проблема возвращать модели алхимии (по сути дата-маппер) из репозиториев, которые работают с БД?
Учитывай, что ненужных слоев и шлюзов здесь нет.
Звучит как некая вкусовщина, ведь мы сами определяем и договариваемся внутри команды, что будут возвращать наши репозитории. Не вижу ничего плохого в том, что они будут возвращать те сущности, с которыми работают (модельки алхимии).
Если нам это надо сконвертить в тот тип, который необходим приложению, то это всегда можно сделать внутри сервиса. Не вижу смысла смешивать репозитории, которые работают с таблицами и бизнес-представление наших сущностей.
Ну так базовый репозиторий не обязывает использовать термины базы данных для построения запросов в бизнес логике. Более того, в бизнес логике запросы как раз строить не стоит. Если нужен какой-нибудь кастомный
def find(name: str| None, price: int | None)
, то его всегда можно написать внутри репозитория.Рассмотрим пример. В репозитории из
advanced-alchemy
есть метод,get_one_or_none(...)
. Как следует из названия, он получает сущность, если нашел ее по фильтрам или возвращаетNone
в ином случае. Я бы не сказал, что это термин базы данных, а в бизнес логике такое использовать крайне удобно.Ну не соглашусь, переменные окружения никому ничего не должны. Ведь бывают случаи, когда ты хочешь их из .env вытащить, вполне нормальная практика, многие так делают, и ничего криминального в ней нет. И как раз в этом случае придется вызывать dotenv.
По поводу формата енвов уже соглашусь, иметь какой-то более понятный форматы было бы намного лучше.
Но в конечном счете, pydantic_settings позволяют тебе довольно гибко загружать нужные переменные, как из окружения, так и из .env файликов. Обязательно ли всегда использовать именно этот пакет? Нет, конечно, но сам по себе пакет предоставляет много функций и отказываться от него в пользу более простого решения только ради того, чтобы .lock файл сократить, как будто нет смысла.
Тут стоит сказать, наверное, что любая ОРМ приносит за собой какой-то непонятный интерфейс. Намного проще писать на голом SQL, если нам не нравятся кривые интерфейсы. Но тогда мы лишаемся преимуществ, которые приносят ОРМ.
Мне кажется, что подобные решения это всегда какой-то трейдофф. И в данном случае пользы больше, чем неудобств, на мой взгляд.
Согласен, это может иногда путать, и мне это тоже не нравится, честно говоря. Но такие приколы легко отлавливать на этапе ревью
Не вижу ничего плохого в том, что у нас есть объект, отвечающий за переменные из енвов. Его крайне удобно использовать и мокать в случае чего.
Я про запутанные конфиги и не говорил, в
BaseSettings
у тебя есть конфиг, в котором можно указать префикс для всех полей в настройках.Также твой подход через
**os.environ
удобен в том случае, у тебя основной объект настроек не содержит в себе вложенные настройки. В случаеpydantic_settings
такие вложенные настройки так же будут подтягиваться из енвов.Тут, наверное, моя недоработка, стоило показать аннотациями, что репозитории возвращают. Если ты под "доменными объектами" имеешь в виду модельки алхимии, то все так и есть, как ты описал.
По поводу наследования базового репозитория вот, что могу сказать: это строго говоря необязательно, но если ты понимаешь, что есть какие-то методы, которые стоит пошарить несколькими репозиториями, то всегда можно сделать свой базовый репозиторий на основе предоставленного либой. Естественно, эти методы должны работать с теми же объектами, что и базовыый репозиторий. В данном случае это модели алхимии
Стоит ли писать репы с 0? А стоит ли изобратать свой собственный велосипед? Я считаю, что если есть какая-то рабочая штука, закрывающая мои нужды, то нет смысла утруждать себя и писать собственную.
Тогда тебе придется еще и dotenv где-то вызывать. А если тебе надо какие-то префиксы добавить в свои сеттинги? Это нужно в том случае, если у тебя много сервисов, и атрибуты настроек имеют одинаковое название.
В твоем примере тебе придется называть свои переменные по-разному или везде писать алиас, а с использование pydantic-settings достаточно один атрибут в конфиг добавить.
А вытаскивать переменные руками из окружения - довольно неудобная и избыточная практика. Намного проще, когда они вытаскиваются по названию автоматически и лежат сразу в одном месте. У тебя так не возникает вопросов, откуда та или иная переменная, ты сразу знаешь
У нас может быть немного нестандартный подход, так как мы практически не используем
.env
файлы. Вся разработка и тестирование проходят через docker compose, поэтому необходимые переменные обычно хранятся в самих настройках в виде значений по умолчанию. Если же возникает необходимость запустить сервис вне Compose, можно использовать.env
файл, но это скорее исключение, а не рекомендованная практика.На средах у нас k8s, и там переменные передаются через конфиг мапы, а чувствительные данные — через vault. Далее данные из эти источников попадают в переменные окружения переменными окружения, откуда их уже считывает объект settings.
Привет! У нас нет релизных веток, все релизы происходят через теги.
Если что-то где-то поломалось, то мы просто делаем новый merger-request, в котором фиксим проблемы.
Как такового fix flow у нас нет. Если надо что-то поправить, мы просто делаем новый исправляющий релиз.
Да, у нас пока нет таких больших проектов. Все SAST проходят сразу на мастере, занимает около 3-ти секунд.
У нас есть две тестовые среды: дев и превью.
На дев мы раскатываемся во время разработки, превью при этом не задействуется.
Деплой на все тестовые окружения происходит только после создания тега. После чего на превью среде, куда раскатился код под новым тегом, будет произведено тестирование нашего релиза.
Ну и если все хорошо - едем на прод.
Привет! Выглядит очень прикольно на самом деле.
Единственное чего пока не увидел из коробки - это redis sentinel, а у нас он везде используется.
Возможно, это мои личные приколы, но я не очень понимаю, зачем изобретать велосипед и хранить инфру в коде, когда для этого уже есть хорошее и надежное решение в виде композа. Хотя, не отрицаю, что это может быть вполне удобно, надо потестить.
В целом, выглядит как довольно хорошее решение для локальной разработки и CI.
Тогда нам очень повезло, что у нас такого нет.)
В любом случае, в джобе статического анализа имеется переменная VERSION. С ее помощью ты можешь зафиксировать версию инструмента, если не хочешь, чтобы устанавливалась самая новая версия.
Привет! Мы посчитали, что лучше уж мы нагрузим раннеры и подождем полторы минутки на merge-request'е, чем будем гонять этот mypy только локально. Ведь если гонять его только локально - можно пропустить обновление и не исправить ошибки.
Mypy - это тайпчекер и если он сыпет ошибками, то это хорошо, ведь это его основная задача
А какое решение здесь видишь ты?