С

Советы разработчикам (python и не только)

@advice17

Советы для разработчиков ПО от @Tishka17Поддержать материально https://www.tinkoff.ru/cf/2NkdXaljivIProgramming, python, software architecture и все такое

8 502 подписчиков
РедкоКачество: 77%🇷🇺 RUПоследний пост: 07.01.2024
Открыть в TelegramПоделиться в TG

Последние посты

С
Советы разработчикам (python и не только)@advice17

Data Transfer ObjectКогда мы общаемся с удаленным кодом (посылаем запросы, сообщения), пересылаемые данные в коде зачастую удобнее передавать совместно и представлять не в виде отдельных параметров методов, а в виде некоторой структуры. Она называется DTO - Data Transfer Object (объект передачи данных).DTO - любой объект/структура данных без своей логики, пригодная для сериализации для передачи по сети. При этом не обговаривается как именно она будет сериализована - она может содержать специальные методы, или этим может заниматься отдельный код (на основе интроспекции, макросов или как угодно).DTO - это, в первую очередь, назначение объекта. Это данные, которые надо передать. Могут иметься в виду входящие, так и исходящие данные.1. Для существования DTO не требуется наличие каких-либо доменных моделей, это любые данные. Они могут собираться из других DTO, нескольких бизнес-сущностей или вообще генерироваться на ходу2. Сериализация возможна в совершенно разные форматы (например: xml, json, protobuf). При это не обязательно использование одного DTO под несколько форматов3. DTO может использоваться в разных адаптерах приложения: для данных, возвращаемых или принимаемых обработчиком сервера, из клиентов внешних апишек, как результат работы DAO и т.п. В целом, структуры, передаваемые между слоями приложения без удаленных вызовов, могут тоже называться DTO.4. Если DTO содержит логику сериализации, мы обязаны ограничивать его использование на внешнем слое приложения. То есть, при возврате данных из интерактора мы должны логику их сериализации вынести наружу.5. DTO не содержит логики, но содержит информацию об структуре данных и общеизвестных типах. Парсер DTO может содержать какие-то универсальные предохранители от загрузки слишком больших данных. Но, например, кастомизация длины строки или допустимого диапазона чисел на каждое поле - однозначно будет ошибкой.6. DTO на сервере и клиенте могут иметь совершенно разную реализацию и она может меняться независимо, однако структ

27 нояб. 2024 г.16 400В Telegram
С
Советы разработчикам (python и не только)@advice17

Полиморфизм при наследовании и LSP.Когда мы строим иерархию объектов, мы часто делаем одноименные методы с разным поведением. Если в родительском классе такой метод отсутствует, то мы в целом вольны в наследниках делать что захотим.Если же родительский класс содержит такой метод, то у нас есть следующие варианты:1. Реализация в дочернем классе полностью сохраняет внешнее поведение метода (параметры, результат, побочные эффекты), но отличается реализацией и как следствие нефункциональными характеристиками (например, производительностью). В этом случае классы полностью взаимозаменяемы.2. Реализация в дочернем классе полностью сохраняет поведение родительского класса, но делает дополнительную работу или меняет поля, отсутствующие в родительском классе. Мы все также можем использовать дочерний класс там, где ожидается родительский, но в других частях программы мы получаем дополнительные возможности.3. Мы меняем поведение метода родительского класса, но не нарушаем его важные характеристики. В этом случае мы должны четко понимать, какие требования есть к базовому методу, чтобы не нарушить совместимость. Если мы не соблюдаем принцип инверсии зависимости и базовый класс не является абстрактным, может получиться, что требования к методу слишком конкретные и тогда этот вариант фактически сводится к предыдущему. При этом мы можем расширять область значений параметров метода (снимая некоторые ограничения или переходя к родительским типам), а иногда и сужать область значений результата.4. Мы сохраняем формальные характеристики метода (сигнатуру, возвращаемое значение), но сильно меняем его поведение. Как правило, это происходит когда требования к методу не выделены или по ошибке. В этом случае инструменты, предоставляемые языком программирования, могут предполагать что методы все ещё совместимы, что не является правдой на самом деле.5. Мы меняем даже сигнатуру метода несовместимым образом. Например, произвольно меняем тип результата или параметров, но не так как в п.3. Класс од

14 окт. 2024 г.17 200В Telegram
С
Советы разработчикам (python и не только)@advice17

Аутентификация и IdentityProviderДля реализации идентификации и аутентификации мы неизбежно используем данные, не нужные основной логике приложения, а логика может быть достаточно сложной сама по себе:• Для событий телеграм идентификация происходит на основе данных из события. Аутентификация пользователя не производится - мы только проверяем безопасность соединения с сервером• Для бэкенда веб приложения мы часто используем сессии. В этом случае мы достаем их из cookie и дальше проверяем в какой-либо базе данных, откуда и достаем идентификатор пользователя, соответствующего сессии.• Для API в микросервисной среде мы можем использовать JWT-токены, содержащие айди пользователя, которые проверяются на основе подписи.• В некоторых сервисах мы можем полагаться на пользовательские TLS-сертификаты, заверенные сертифицирующем сервисом• Проверка токена или сертификата может делаться как в коде приложения, так и на реверс прокси.• При разработке или тестировании может использоваться фиксированный пользователь с определенными правами.Множество вариантов реализации усложняется тем, что они могут использоваться одновременно с одной и той же бизнес логикой. Это приводит к необходимости выделения интерфейса (IdentityProvider), скрывающего эти детали. Обращаю так же внимание, что такой объект не должен возвращать данные, относящиеся к текущему контексту приложения. Грубо, его можно свести к чему-то такому:class IdentityProvider(Protocol): def get_current_user_id(self) -> int: ... def get_current_user_roles(self) -> list[Role]: ...В простом случае реализация этого интерфейса является небольшим инфраструктурным сервисом, но в перспективе является прослойкой между бизнес логикой приложения и отдельным контекстом, занятым различными вопросами управления пользовательскими сессиями и авторизационными данными. Например, обработчики этого контекста могут заниматься обработкой процедуры логина в сервис, очисткой пользовательских сессий по его команде и т.п. Наши классы бизнес логики пр

20 сент. 2024 г.17 200В Telegram
С
Советы разработчикам (python и не только)@advice17

Аутентификация и авторизацияНаши приложения выполняют разные сценарии и для некоторых из них может быть важно, что за пользователь перед нами. То есть, для целей бизнес-логики может быть необходимо получить некоторые уникальные данные пользователя, которые позволят его отличить от других - это идентификация. Реализуется она различным способом: иногда мы можем явно спросить у пользователя, кто он, иногда мы получаем информацию из сетевых пакетов или системы. Идентификационные данные дальше могут использоваться по-разному: их можно записать в лог, использовать как ссылку на владельца при создании объектов в системе или в различных проверках внутри нашей логики.Идентификация должна выполняться безопасно: иногда пользователь может попытаться выдать себя за другого. Процесс проверки, что пользователь не обманывает нас в том, кто он - аутентификация. Она не всегда актуальна: если мы получили сообщение от telegram, мы можем верить информации об отправителе, потому что доверяем серверам телеграма. Однако, если мы получили HTTP запрос, мы должны принять меры для обеспечения защиты от подделки личности пользователя (аутентифицировать его).Когда пользователь первый раз обращается к нашему сайту, мы обычно отправляем его на сценарий входа (первичная аутентификация, login, sign in). Этот сценарий может быть достаточно сложным, состоять из нескольких шагов (например в случае двух- и многофакторной аутентификации), требовать использовании СУБД и внешних сервисов. Процедура входа скорее всего будет отделена от основной части приложения или даже реализовываться внешней системой (например, Keycloak). Иногда процедуру логина на сайт называют "авторизацией на сайте", но не следует это путать с авторизацией действий (см. ниже). В случае веб-приложений, после первичной аутентификации мы часто используем различные токены для того, чтобы в последующих действиях было проще его аутентифицировать. Проверка таких токенов связана с протоколом доставки, может задействовать базы данных и снова вы

17 июн. 2024 г.20 400В Telegram
С
Советы разработчикам (python и не только)@advice17

У ребят из Podlodka Python Crew стартует новый сезон онлайн-конференции, тема — инфраструктура. Всё проходит онлайн, с 3 по 7 июня. Я буду выступать там с докладом про Dependency Injection и dishka непосредственно. Все доклады записываются, так что смотреть их день в день необязательно.Что будет• Мой доклад про DI• Погружение в трейсинг: чем он полезен, как работает и как его внедрить.• Поиск уязвимостей: практические задания с разбором• Рассказ про неочевидные кейсы оптимизации.• Обучение эффективному мониторингу: типы метрик, как их собирать и экспортировать. И ещё много всего.Конференция платная, но специально для подписчиков промокод INFRA_17 на скидку 1000рЗапись выступления доступна тут: http://www.youtube.com/watch?v=gWOBaZ3I4gc

30 мая 2024 г.17 600В Telegram
С
Советы разработчикам (python и не только)@advice17

Unit of workПаттерн Unit of work (единица работы) предназначен для того, чтобы следить за изменениями объектов и потом координировано их сохранять в базу данных.Это позволяет:• Ограничить время жизни транзакции• Не выполнять обращение к БД сразу при выполнении изменений, а значит попытаться сделать это более эффективно• Более удобно следить за изменениями в случае сложной иерархии или большого количества типов моделей.Принцип использования Unit of Work состоит из двух этапов:1. Сначала мы регистрируем в нем, что с нашими моделями были изменения (register_new, register_dirty, register_deleted).2. Затем в какой-то момент сохраняем все эти изменения в БД (commit)Изменения могут регистрировать как сами модели, так и прикладной код, использующий их. Таким образом, каждый раз, когда мы что-то делаем с моделями (добавляем, удаляем, изменяем), мы не отправляем сразу запрос в БД, а вместо этого добавляем эти изменения в UoW для последующего сохранения.Хотя Unit of Work имеет метод для коммита изменений, он является более сложной вещью чем просто управление транзакциями. Суть его в том, чтобы накапливать изменения перед отправкой в базу данных. При этом он может выполнять оптимизации запросов, например, объединяя вставку данных в одну таблицу в один запрос. Также, в нем может быть реализована логика контроля целостности данных, например, с помощью оптимистических блокировок.Сам Unit of work обращается в БД не напрямую, а через отдельные объекты, реализующие паттерн Data Mapper. Условно, в данном случае, каждый такой объект умеет отправлять в БД изменения (insert, update, delete) модели определенного типа и UoW знает в какой из мапперов обращаться для каждой из сохраненных моделей. Обратите внимание, что Unit of Work не используется для доступа к мапперам / шлюзам к БД, его задача другая. Более того, использование его в таком смысле будет нарушением принципа разделения интерфейсов.С концепциями Unit of Work и Data Mapper тесно связан паттерн Identity Map, когда мы храним реест

12 мая 2024 г.18 800В Telegram
Советы разработчикам (python и не только) — пост в ТГ канале
С
Советы разработчикам (python и не только)@advice17

Dishka - IoC-контейнер для PythonКогда мы следуем подходу Dependency Injection, а особенно - слоистой архитектуре, у нас образуется отдельная группа функций и классов, выполняющих только одну задачу - создание других объектов. Такой код лучше держать поближе к main, так как он связывает воедино разные части приложения и связан с конфигурацией запуска.В сложном приложении такой компонент может содержать большое количество функций, контролировать как создание, так и корректную очистку объектов и, что самое главное, их взаимосвязь. Для упрощения работы с такими фабриками придумали отдельный тип библиотек - IoC-контейнеры (DI-фреймворки).В Python меня долго не устраивали существующие контейнеры и я решил сделать свой:Хочу представить вам Dishka 1.0Цель этого проекта - предоставить простой и удобный IoC-контейнер, который сможет забрать всю работу с зависимостями. Мне кажется, на текущий момент это самый функциональный вариант контейнера, имеющий при этом самое простое API.• Вы можете использовать его с любым фреймворком, но для некоторых мы уже подготовили хелперы• Для создания зависимости можно указать отдельную функцию или использовать __init__ класса• Зависимости имеют ограниченное время жизни (скоуп) и вы сами управляете им• Зависимости кэшируются, поэтому один и тот же объект может быть переиспользован пока он жив. Так можно передать одно соединение с БД в несколько гейтвеев• Фабрики зависимостей можно группировать в классы и компоненты, что позволяет делать контейнер модульным• Можно декорировать объекты, использовать один объект для нескольких типов• При старте проверяется корректность конфигурации контейнера, что позволяет исключить многие ошибкиЧто значит версия 1.0?У библиотеки было 9 промежуточных релизов, мы рады объявить, что закончена вся работа по стабилизации её интерфейса и исправлению ошибок. И у нас есть планы по развитию, уникальные фичи сами себя не напишут.Будем рады новым пользователям, багрепортам, запросам фич и звездам на гитхабе.Видео с Podlod

3 апр. 2024 г.19 400В Telegram
С
Советы разработчикам (python и не только)@advice17

Абстрактные классы и интерфейсыЕсли рассуждать, не привязываясь к языку программирования, то:Абстрактный класс - это заготовка для класса. В нем часто есть методы с реализацией и методы, помеченные как абстрактные. Экземпляры такого класса напрямую создавать нельзя. Нужно отнаследоваться от него и заполнить пропущенные методы.Абстрактный класс может содержать данные, обычные методы. Его отличает именно наличие абстрактных методов. В некоторых языках - это методы без тела (C++, Java), в некоторых (Python) - методы со специальной пометкой. Чтобы наследник класса перестал быть абстрактным, надо реализовать в нем все такие методы.Интерфейс же - это требования к тому, что должен уметь объект. Это набор сигнатур операций. Как правило, речь о наборе названий методов, их параметрах и типе результата, но иногда речь и про доступ к атрибутам. В общем случае, интерфейс может не существовать в коде как именованная сущность.Интерфейс существует просто по факту того что вы написали. Если ваша функция принимает объект и вызывает у него методы foo() и bar(), требуемый ей интерфейс можно выразить как "объект с методами foo и bar, которые не требуют аргументы". Если у вас есть класс с методами foo и bar, то его экземпляры удовлетворяют интерфейсам "любой объект", "объект с методом foo", "объект с методами foo и bar" и др.С практической стороны работа с интерфейсами отличается от языка к языку:• Python проверяет соответствие объекта ожиданиям функции по факту вызова операций с ним во время выполнения кода. Сторонние линтеры могут проверять это другим способом, ориентируясь на аннотации типов или ещё как-то. Для того чтобы выразить требования к интерфейсу в тайпхинтах, мы можем оформить класс, наследующийся от Protocol. Для реализации такого интерфейса достаточно реализовать соответствующие методы, но можно и наследоваться от него для упрощения поиска ошибок.• В Golang интерфейс описывается в коде с помощью ключевого слова interface. В дальнейшем он используется как тип переменных или

22 мар. 2024 г.16 200В Telegram
С
Советы разработчикам (python и не только)@advice17

Виртуальные окружения PythonВо многих случаях при разработке приложений на Python нам требуются сторонние библиотеки. Однако, если мы будем их устанавливать в глобальное окружение, мы в какой-то момент столкнемся с конфликтами между разными проектами, нам будет сложнее производить очистку такого окружения. А в некоторых ситуациях мы можем даже сломать системные приложения.Чтобы избежать таких проблем, рекомендуется практически всегда использовать виртуальные окружения. Это специальная папка, куда устанавливаются библиотеки и которых может быть больше одной на вашем компьютере. В python 3 есть встроенное средства для управления ими - пакет venv, но есть и сторонние популярные решения такие как virtualenv, poetry и многие другие.Для того чтобы создать новое виртуальное окружение, выполните команду с указанием нужного вам питона:python -m venv имя_папкиПосле этого в каталоге имя_папки будет создано множество служебных файлов и установлен pip. Часто в качестве папки указывают venv или .venv.Для того чтобы работать внутри виртуального окружения, вы можете:a. Запустить команду с указанием пути. Например, python, pip или поставляемые сторонними пакетами. На Linux это будет ./имя_папки/bin/python, на Windows - имя_папки\Scripts\python (пути могут быть относительные или абсолютные). Это бывает удобно внутри скриптов или файлов сервисов. В этом случае, sys.path будет содержать каталог библиотек внутри виртуального окружения. Учтите, что так как переменная PATH не меняется, то запуск других команд (например, через subprocess) без указания пути будет фактически происходить вне виртуального окружения.b. Активировать его внутри вашей командной оболочки (основной сценарий). Для bash/zsh это source ./имя_папки/bin/activate. Для Windows CMD - имя_папки\Scripts\activate.bat. После этого в рамках сессии вашего шелла будет изменена переменная окружения PATH, что приведет к изменению команд, доступных без указания пути. Соответственно, если таким образом будет запущена команда (python,

9 янв. 2024 г.18 700В Telegram
С
Советы разработчикам (python и не только)@advice17

Dependency InjectionПринцип внедрения зависимостей, будучи достаточно простым, концептуально оказывается часто неочевидным.Суть его в том, что когда у нас одному из объектов требуется другой, то он не создает или ищет его сам, а принимает извне. Например, если вашей функции нужно соединение с БД, то она не должна ни импортировать его, ни брать из глобальной переменной, ни создавать сама. Ей это соединение должны передать.Само собой, какой-то код будет создавать эти зависимости, и тут мы стараемся отделять его от кода, использующего их. Благодаря этому:• во-первых, делаем этим зависимости более явными;• во-вторых, можем управлять тем, будет ли использован один экземпляр зависимости или разные;• в-третьих, можем использовать один и тот же код с разными реализациями зависимостей.Представьте, что вашему классу нужны некоторые параметры конфигурации, которые влияют на его поведение, и вы хотите протестировать разные варианты. Если бы класс сам грузил настройки, то вам пришлось бы в тестах учитывать, как именно он это делает, и возможно манипулировать теми объектами, которые обычно не меняются в процессе работы программы. Если же код класса получает эти настройки извне, то вы просто сделаете несколько вызовов с разными настройками. И даже если код класса изменится, тесты останутся корректными.Можно выделить три способа внедрения зависимостей:1. Внедрение через параметры функции/метода. Просто передаем зависимость как ещё один параметр: def clear_users(cursor): cursor.execute("TRUNCATE users;")cursor = connection.cursor()clear_users(cursor)clear_users(cursor)2. Внедрение через параметры конструктора: class UsersDAO: def __init__(self, cursor): self.cursor = cursor def clear_users(self): self.cursor.execute("TRUNCATE users;")dao = UsersDAO(connection.cursor())dao.clear_users()dao.clear_users()3. Внедрение через атрибуты экземпляра (см. так же двухфазная инициализация): class UsersDAO: def clear_users(self): self.cursor.execute("TRUNCATE users;

7 янв. 2024 г.16 400В Telegram

Похожие каналы