Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Делаем шаблон типового микросервиса

Denis Anikin
December 03, 2023

Делаем шаблон типового микросервиса

Когда кода и людей становится много, рано или поздно все приходят к мысли о приведении всего к единообразию. В докладе я расскажу про практики, сложившиеся в его команде, и как их можно перенять вне зависимости от размера проекта.

Выступление для https://podlodka.io/pythoncrew

Denis Anikin

December 03, 2023
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. Денис Аникин 4 Что я делаю — работаю в Райфе

    — lead разработки в 3+ командах — community lead в Python Community — fullstack: typescript, python, devops — выступаю на конференциях и митапах https://xfenix.ru
  2. Опишу проблематику 6 —У нас есть повторяемые микросервисы —Их достаточно

    много (от 3, например) —Мы хотим надежности в каждом сервисе —У нас есть много похожих базовых настроек
  3. Список важных штук 8 —Кодовый стиль и подход к кодовому

    стилю —Автоматизации кодового стиля —Общие настройки и подходы, папки, файлы —Минимум копипасты —Общий CI/CD
  4. А что тут сказать? 11 —«Базовый» асинхронный фреймворк (альтернатива: litestar)

    —Идеально для микросервисов, базирующихся на REST
  5. Осторожные замеры показали 14 —Granian значительно обходит uvicorn по RPS

    и Latency —Я делал об этом доклад на pitepy https://github.com/xfenix/piterpy-imya-mne- skorost —Просто заменой ASGI сервера, который для большинства представляет из себя, по сути, почти невидимую прослойку, мы получаем значительное ускорение вашего бекенда
  6. Наш выбор 18 —Dependency Injector —Создаем (для начала) общий ioc.py

    и кладем зависимости туда —Вдвойне полезно, если ваши ручки перемешиваются с EDA архитектурой (появляются консьюмеры и продюсеры)
  7. 19 Пример class IOCContainer(containers.DeclarativeContainer): orm_engine_replica = providers.Resource(resources.create_engine_resource) orm_session_factory_replica = providers.Resource(resources.create_session_factory,

    engine=orm_engine_replica) dialogs_orm_repository = providers.Factory(DialogsORMRepository, session_factory=orm_session_factory_replica) private_users_orm_repository = providers.Factory( PrivateUsersORMRepository, session_factory=orm_session_factory_replica ) speech_analytics_service = providers.Factory( SpeechAnalyticsService, dialogs_repository=dialogs_orm_repository, private_users_repository=private_users_orm_repository, ) httpx_connection: providers.Resource[httpx.AsyncClient] = providers.Resource(resources.create_httpx_resource) database_master_connection: providers.Resource[Database] = providers.Resource(resources.create_database_resource) kafka_producer: providers.Resource[generic_producer.AsyncKafkaProducer] = providers.Resource( resources.create_kafka_resource ) ...
  8. 23 Пример class RetrySession(Session): @backoff.on_exception( backoff.expo, (psycopg.DatabaseError, sqlalchemy.exc.SQLAlchemyError), max_tries=settings.postgres_connection_tries, )

    def execute( self, statement: Executable, params: _CoreAnyExecuteParams | None = None, ... ) -> Result[typing.Any]: return super().execute( statement=statement, params=params, ... )
  9. Обосновываю почему 27 —Это популярные инструменты —Вам не нужно делать

    очень сложную конфигурацию из 30 плагинов —Вам не нужно делать специальную тайную конфигурацию в стиле «ты не поймешь» (тм), в стиле «для тех кто понимает и копает вглубь» (тм) и т.п. —Легкодоступная информация и легко воспроизводимая конфигурация
  10. Ещё бонус 31 —Ruff недавно представил ruff formatter —Drop-in replacement

    для black —НО, есть отличия https://docs.astral.sh/ruff/formatter/black/ —НО (2), есть несовместимые правила линтера https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
  11. pyproject.toml 35 Для конфигурации ruff (в бонус пресет для тестов)

    [tool.ruff] fix = true unsafe-fixes = true line-length = 120 select = ["ALL"] ignore = ["D1", "D203", "D213", "FA102", "ANN101"] [tool.ruff.isort] no-lines-before = ["standard-library", "local-folder"] known-third-party = [] known-local-folder = ["whole_app"] [tool.ruff.extend-per-file-ignores] "tests/*.py" = ["ANN401", "S101", "S311"]
  12. Некоторые правила мы все же отключаем 37 Лишь небольшая часть

    —I (возможно) так как передать конфигурацию глобально невозможно. Если вы индвидуально запускаете ruff в каждом проекте, то можно использовать pyproject.toml и отказаться ещё и от isort! —TRY003 писать message в exception это нормально —D1 можно пропускать все докстринги —ANN101 можно отключать, если вы не хотите везде подписывать typing.Self, так как он нормально выводится mypy сейчас автоматически —EM если вы любите добавлять сообщения в ваши эксепшены —FBT довольно спорный набор правил, запрещающий bool аргументы —(помним) про несовместимую простыню исключений для format https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
  13. Некоторые правила для тестов 38 Лишь небольшая часть —S101 (вы

    наверняка хотите ассерты в тестах) —S311 (random в тестах — приемлимо) —ANN401 (иногда хочется any)
  14. Другие интересные нюансы 41 —Более 600700 правил и число растет

    быстро —Есть кеширование —Дружит с монорепами
  15. Как этого достичь? 43 —Вам нужен vscode —Вам понадобятся стандартные

    плагины mypy и ruff (black и isort, считай, мы уже выселяем)
  16. 44 Vscode конфигурация "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnPaste": true, "editor.formatOnType":

    true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true, "source.organizeImports": true } },
  17. Как стартануть? 46 Я знаю, может быть не просто —Берете

    ruff —Берете mypy —Включите режим strict в mypy —Включаете все правила в ruff —Они вас заставят J
  18. Стремитесь к покрытию кода в 100% и strict в mypy

    Аннотации tips & tricks 47 —Освойте не только стандартные типы, но и Literal и особенно Final —Final имеет смысл аннотировать все переменные вообще по-умолчанию (чаще всего изменение переменных и ведет к ошибкам) —Примитивные типы можно не аннотировать (a = 5 не надо расписывать как a: int = 5) —Дженерики —Annotated помогает в валидации —В модуле гораздо больше всего, чем Union, Optional, Dict, List и т.п. —сast, overload, final, never, noreturn, typealias, newtype, classvar, typeguard, unpack, Protocol, assert_type, assert_never, TYPE_CHECKING — полезные инструменты
  19. 48 Пример с Final: до some_value = 5 some_another_value =

    'kek’ for one_element in range(100): some_another_value = some_another_value * some_value some_value += 1
  20. 49 Пример с Final: после import typing some_value: typing.Final =

    5 # <- тут само выведет typing.Final[typing.Literal[5]] some_another_value: typing.Final = 'kek' for one_element in range(100): some_another_value = some_another_value * some_value # <- тайпчекер поймает some_value += 1
  21. Как настроить телеметрию? 63 —Можно запустить автоматический инструментатор (opentelemetry-instrument), обернув

    в него запуск вашего сервиса https://opentelemetry.io/docs/instrumentation/python/automatic/example/#execute-the- automatically-instrumented-server —Либо настраивать вручную https://opentelemetry.io/docs/instrumentation/python/ (мы скорее идем по этому пути) —Кроме телеметрии вы можете собирать метрики —И так же сразу можете настроить отдачу метрик https://opentelemetry.io/docs/instrumentation/python/exporters/#prometheus с помощью prometheus-client и opentelemetry-exporter-prometheus
  22. Что сюда входит? 68 —Настраиваем конфигурацию structlog для json логов

    —https://www.youtube.com/watch?v=d2WcVUbKdbw не используем loguru —Не используем асинхронный логгер —Настраиваем MemoryHandler —* В идеале неплохо ещё добавить span id и trace id из open telemetry
  23. Так что же взять? 71 —Имеет смысл брать docker-compose —Добавляем

    в него необходимые инфраструктурные зависимости —Запускаем в нём тесты
  24. Бонус-трек 81 Мы не используем (ещё), но вы можете нас

    обогнать… —Wily —Cohesion —Autoimport —Vulture
  25. Почему? 84 —Kaniko быстрый и не требует рута —Во всем

    приятнее собирать, чем с помощью docker, однако —Вы хотите --mount secret функциональность. Тогда ваш вариант: официальный docker c syntax 1.3
  26. 1. Основной layout, code style 91 —Cookiecutter —Бонус: scaraplate —Общий

    гайд —Здесь у нас лежат pyproject, нужные папки, кубер манифесты (бонус: вы можете сделать наследие от общего helm чарта, но мне эти универсальные чарты не близки)
  27. 2. CI/CD 93 Gitlab version —Централизованный пайплайн с помощью include

    —Внутри имеет смысл обильно использовать reference и наследование —Имеет смысл версионировать шаблоны —Имеет смысл делать пресеты —Немного устаревший, но нормальный пример https://github.com/xfenix/pycon2022
  28. 3. Базовая настройка сервиса 95 —Мы делаем свой пакет —Этот

    пакет позволяет «обернуть» настройку в одну функцию и прокинуть туда все нужные опции (бонус: можно совместить с pydantic base settings) —В неё входит: opentelemetry, sentry/glitchtip, logging
  29. def build_app() -> FastAPI: fastapi_app = FastAPI() bootstrap_fastapi( fastapi_application=fastapi_app, sentry_parameters={

    "sentry_dsn": settings.sentry_dsn, }, opentelemetry_parameters={ "opentelemetry_endpoint": settings.opentelemetry_endpoint, "service_name": settings.awesome_service, "namespace": settings.namespace, "service_version": settings.version, "container_name": settings.container_name, "instruments": [ RedisInsrumentator(), AioPikaInstrumentator(), ] }, ) return fastapi_app Как может выглядеть
  30. Ссылки/исходники 102 —https://opentelemetry.io/ecosystem/registry/?language=python&component=instrumentation тут можно найти каталог интеграций с телеметрией

    —https://github.com/stars/xfenix/lists/python-code-style подборка утилит для качества кода —https://github.com/cookiecutter/cookiecutter —https://github.com/rambler-digital-solutions/scaraplate —https://github.com/xfenix/pycon2022 —https://www.youtube.com/watch?v=d2WcVUbKdbw почему structlog —https://www.youtube.com/watch?v=G3JKtB8tgvg полный доклад про codestyle —https://github.com/xfenix/pycon2022/blob/main/scripts/auto-semver.py