ПРИНЦИП ВНЕДРЕНИЯ ЗАВИСИМОСТЕЙ В ЯЗЫКЕ ПРОГРАММИРОВАНИЯ PYTHON Могилатов Р.К.
Могилатов Роман Константинович - ведущий инженер по направлению Python,
Компания «СофтСерв», г. Днепр, Украина
Аннотация: данная научная работа исследует применение принципа внедрения зависимостей в языке программирования Python.
Ключевые слова: внедрение зависимостей, шаблоны проектирования, Python, программирование, разработка программного обеспечения.
Принцип внедрения зависимостей
Принцип внедрения зависимостей это принцип, который помогает снизить связывание (low coupling) и увеличить сцепление (high cohesion) в разрабатываемом программном обеспечении на Python. Принцип внедрения зависимостей это так же один из способов добиться инверсии управления (inversion of control). Инверсия управления, в свою очередь, это принцип, который позволяет повысить гибкость разрабатываемого программного обеспечения на языке программирования Python, что значительно улучшает коэффициент переиспользования программных компонентов. Так же гибкость, достигнутая в результате использования внедрения зависимостей и инверсии управления, позволяет достигнуть значительных улучшений в сфере тестирования и автоматизации разработки программного обеспечения.
Связывание и сцепление в разработке программного обеспечения это особенности степени связи программных компонентов.
Высоким связыванием называют высокую степень интеграции компонентов. В случае высокого связывания компонентов, компоненты проблематично или невозможно дизинтегрировать. В сфере электроники это можно сравнить с пайкой, в металлургии - со сваркой. Обратная интеграция условно-проблематична или условно-невозможна при высоком связывании. В противовес, при низком связывании программные компоненты можно дизинтегрировать без особых усилий -компонентные связи легко разрываются и создаются заново.
Обратной стороной высокого связывания является высоко сцепление. При высоком сцеплении компоненты обладают интерфейсами пригодными для прозрачной интеграции и дизинтеграции. Примером интерфейсов с высоким сцеплением в мире электроники могут быть стационарные интерфейсы RS-232, COM, USB и другие - компонентные связи легко разрываются и новые связи создаются так же легко. Это приводит к увелечению гибкости программного обеспечения.
Принципы связывания и сцепления в разработке программного обеспечения имеют высочайшую корреляцию. Высокое связывание (high coupling) подразумевает низкое сцепление (low cohesion), и, соответственно, низкую гибкость (low flexibility). Высокое сцепление (high cohesion) подразумевает низкое связывание (low coupling), и, соответственно, высокую гибкость (high flexibility).
Высокое связывание -> низкое сцепление -> низкая гибкость.
Высокое сцепление -> низкое связывание -> высокая гибкость.
Рис. 1. Обратная зависимость сцепления и связывания
Стремление за высокой гибкостью обусловлено стремлением за ускорением разработки программного обеспечения. При высоком уровне гибкости можно дизасемблировать компоненты системы и переиспользовать их при создании новых программных пакетов. Без высокой степени сцепления этого добиться невозможно. Таким образом, главной мотивацией применения принципа внедрения зависимостей и инверсии управления является стремление к ускорению разработки программного обеспечения.
Как дополнительное преимущество, при применении принципа внедрения зависимостей увеличивается тестируемость. Под увеличением тестируемости подразумевается уменьшение сложности разработки и покрытия программного кода автоматическими тестами.
Внедрение зависимостей в языке программирования Python
Реализацию принципа внедрения зависимостей можно описать следующей фразой: «В программном коде объекты не должны создавать друг друга. Объекты должны предоставлять возможность внедрить зависимость». Рассмотрим внедрение зависимостей на примере. Для реализации трансформации рассмотрим следующий пример на языке программирования Python: import os class ApiClient:
def_init_(self):
self.api_key = os.getenv('API_KEY') # <-- dependency self.timeout = os.getenv('TIMEOUT') # <-- dependency class Service: def __init__(self):
self.api_client = ApiClient() # <-- dependency if __name__ == '__main__': service = Service()
Применим принцип внедрения зависимостей для предыдущего примера: import os class ApiClient:
def_init_(self, api_key: str, timeout: int):
self.api_key = api_key # <-- dependency is injected self.timeout = timeout # <-- dependency is injected class Service:
def_init_(self, api_client: ApiClient):
self.api_client = api_client # <-- dependency is injected if __name__ == '__main__': service = Service( ApiClient( api_key=os.getenv('API_KEY'), timeout=os.getenv('TIMEOUT'),
),
)
В результате применения подхода внедрения зависимостей класс ApiClient не содержит знания о источнике происхождения его конфигурационных параметров. Переменные окружения теперь внедряются в виде инъекций. Это снижает связывание для класса ApiClient: теперь его опции могут быть внедрены в результате чтения конфигурационного файла или выполнения запроса в базу данных.
Класс Service больше не создает класс ApiClient. Вместо этого он предоставляет интерфейс для внедрения зависимости Service через конструктор. Это снижает связывание для класса Service, так как теперь можно передавать экземпляры класса ApiClient инициализированные различным способом.
Результатом применения внедрения зависимостей в программный код примера на языке программирования Python стало снижение связности, увеличения спецпления, и, соответственно, гибкости.
Программный комплекс внедрения зависимостей Dependency Injector Применение внедрения зависимостей имеет свою цену. Происходит дисперсия ответственности поиска и создания зависимостей. Такая дисперсия может привести к дублированию кода инициализации и, соответственно, вызвать повышение когнитивной сложности программного кода на языке программирования Python.
Для устранения дисперсии вызванной применением принципа внедрения зависимостей можно воспользоваться программным комплексом Dependency Injector. Этот программный комплекс абсорбирует обязанности создания и разрешения зависимостей объектов. Это устраняет негативную дисперсию. Для того чтобы применить программный комплекс Dependency Injector нужно создать контейнер зависимостей. Контейнер зависимостей должен содержать информацию о всех компонентах и их связях в явном виде. Применим программный комплекс Dependency Injector к примеру для того чтобы устранить дисперсию обязанностей создания объектов:
from dependency_injector import containers, providers class Container(containers.DeclarativeContainer): config = providers.Configuration() api_client = providers.Singleton( ApiClient,
api_key=config.api_key, timeout=config.timeout.as_int(),
)
service = providers.Factory( Service,
api_client=api_client,
)
if_name_== '_main_':
container = Container()
container.config.api_key.from_env('API_KEY') container.config.timeout.from_env('TIMEOUT') service = container.service() Компонент Singleton создает объект единожды. После создания объекта, объект переиспользуется при внедрении в качестве зависимостей. Компонент Factory создает новый объект при каждом обращении. Его зависимостью является компонент Singleton. Таким образом можно создавать множество объектов класса Service, которые переиспользую единственный экземпляр объекта в оперативной памяти ApiClient.
Компоненты Singleton и Factory называются провайдерами. Программный комплекс дает возможность комбинировать провайдеры для создания сложных графов объектов: provider1()
■> provider2()
-> provider3()
I-> provider4()
-> provider5()
I-> provider6()
Так же провайдеры можно переписывать другими компонентами во время автоматизированного тестирования или настройки развертывания программного комплекса в различных производственных средах. Пример использования переопределения провайдера: from unittest import mock with container.api_dient.override(mock.MockO):
service = container.service() Программный комплекс Dependency Injector простой в применении и быстрый. Он разработан с помощью транслируемого языка высокого уровня Cython. Программный код на языке программирования Cython транслируется в программный код на языке программирования C, а затем компилируется в машинный код. Компиляция в машинный код позволяет добиться наивысшей скорости выполнения. Программный комплекс поставляется в предварительно компилируемом виде для уменьшения времени сборки. Для достижения максимально возможной скорости работы можно выполнять компиляцию на клиентском процессоре дает возможность. Это дает возможность применить все доступные для данного процессора оптимизации машинного кода.
Разработка приложений с применением принципа внедрения зависимостей Программный комплекс Dependency Injector помогает разрабатывать приложения с применением принципа внедрения зависимостей. Программный комплекс предоставляет выбор из нескольких вариантов. Для разработки небольших приложений хорошо подойдет подход с применением единого контейнера внедрения зависимостей. Для разработки крупных приложений оптимальным будет использовать подход мультиконтейнеров.
Рассмотрим пример приложения. Оно состоит из нескольких сервисов предметной области, которые имеют зависимости на базу данных и подключения к веб-сервисам компании Амазон (Amazon Web Services, AWS).
Рис. 2. Диаграмма классов демонстрационного приложения
Пример применения единого декларативного контейнера программного комплекса Dependency Injector для рассматриваемого приложения: import logging.config import sqlite3 import boto3
from dependency_injector import containers, providers from . import services
class C ontainer(containers .DeclarativeC ontainer):
config = providers.Configuration()
configure_logging = providers.Callable( logging.config.fileConfig, fname='logging.ini',
)
# Gateways
database_client = providers.Singleton( sqlite3.connect, config .database .dsn,
)
s3_client = providers.Singleton( boto3.client, service_name= 's3',
aws_access_key_id=config.aws.access_key_id, aws_secret_access_key=config.aws.secret_access_key,
)
# Services
user_service = providers.Factory( services.UserService, db=database_client,
)
auth_service = providers.Factory(
services.AuthService, db=database_client,
token_ttl=config.auth.token_ttl.as_intO,
)
photo_service = providers.Factory( services.PhotoService, db=database_client, s3=s3_client,
)
Запуск приложения с подобным контейнером выглядит следующим образом: import sys
from .containers import Container
def main(email: str, password: str, photo: str) -> None: container = Container()
container.configure_logging() container.config.from_ini('config.ini')
user_service = container.user_service() auth_service = container.auth_service() photo_service = container.photo_service()
user = user_service.get_user(email) auth_service.authenticate(user, password) photo_service.upload_photo(user, photo)
if __name__ == '__main__': main(*sys.argv[1:])
Применение подхода мультиконтейнеров для рассматриваемого примера будет выглядеть следующим образом: import logging.config import sqlite3 import boto3
from dependency_injector import containers, providers from . import services
class Core(containers.DeclarativeContainer): config = providers.Configuration() configure_logging = providers.Callable( logging. config.dictConfig, config=config.logging,
)
class Gateways(containers.DeclarativeContainer): config = providers.Configuration() database_client = providers.Singleton( sqlite3.connect, config.database.dsn,
)
s3_client = providers.Singleton(
boto3.client, service_name='s3 ',
aws_access_key_id=config.aws.access_key_id, aws_secret_access_key=config.aws.secret_access_key,
)
class Services(containers.DeclarativeContainer): config = providers.Configuration() gateways = providers.DependenciesContainer() user = providers.Factory( services.UserService, db=gateways.database_client,
)
auth = providers.Factory( services.AuthService, db=gateways.database_client, token_ttl=config.auth.token_ttl.as_int(),
)
photo = providers.Factory( services.PhotoService, db=gateways.database_client, s3=gateways.s3_client,
)
class Application(containers.DeclarativeContainer): config = providers.Configuration() core = providers.Container( Core,
config=config.core,
)
gateways = providers.Container( Gateways,
config=config. gateways,
)
services = providers.Container( Services,
config=config.services, gateways=gateways,
)
Запуск приложения на базе мультиконтейнера тоже будет отличаться: import sys
from .containers import Application
def main(email: str, password: str, photo: str) -> None: application = Application() application.config.from_yaml('config.yml') application.core.configure_lo gging()
user_service = application.services.user() auth_service = application.services.auth() photo_service = application.services.photo()
user = user_service.get_user(email) auth_service.authenticate(user, password) photo_service.upload_photo(user, photo)
23
if_name_== '_main_':
main(*sys.argv[1:]) Заключение
Применение принципа внедрения зависимостей для разработки програмного обеспечения на языке программирования Python имеет следующие преимущества:
• Повышение гибкости
• Снижение стоимости внесения изменений и будущей разработки
• Увеличение тестируемости
• Снижение когнитивной нагрузки.
Негативным аспектом является дисперсия обязанности инициализации объектов и разрешения зависимостей между объектами.
Применение программного комплекса Dependency Injector позволяет устранить негативную дисперсию за счет абсорбции обязанности управления зависимостями.
Список литературы
1. Refactoring: Improving the Design of Existing Code by Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts, Erich Gamma (Foreword) // Addison-Wesley Professional; 1st edition (June 28, 1999).
2. Patterns of Enterprise Application Architecture by Martin Fowler. Implementation Patterns by Kent Beck // Addison-Wesley Professional; 1st edition (October 23, 2007).
3. The Pragmatic Programmer: From Journeyman to Master by Andrew Hunt, David Thomas // Addison-Wesley Professional; 1st edition (October 1, 1999).
4. Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin // Pearson; 1st edition (August 1, 2008).
5. Building Microservices: Designing Fine-Grained Systems by Sam Newman // O'Reilly Media; 1st edition (February 2, 2015).
6. Design Patterns: Elements of Reusable Object-Oriented Software by GoF - Gang of Four - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides // Addison-Wesley Professional; 1st edition (November 10, 1994).
7. Refactoring: Improving the Design of Existing Code (2nd Edition) by Martin Fowler // Addison-Wesley Professional; 2nd edition (November 30, 2018).
8. Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans // Addison-Wesley Professional; 1st edition (August 20, 2003).
9. The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition (2nd Edition) by Brooks Jr., Frederick P. // Addison-Wesley Professional; Anniversary edition (August 2, 1995).
10. The Innovation Algorithm: TRIZ, systematic innovation and technical creativity (1st Edition) by Genrich Altshuller. // Technical Innovation Ctr; 1st edition (March 1, 1999).
11. Software Estimation: Demystifying the Black Art by Steve McConnell. // Microsoft Press; 1st edition (March 1, 2006).
12. Inversion of Control Containers and the Dependency Injection pattern. [Электронный ресурс]. Режим доступа: https://martinfowler.com/articles/injection.html/(дата обращения: 07.06.2021).
13. Dependency Injector (Documentation). [Электронный ресурс]. Режим доступа: https://python-dependency-injector.ets-labs.org/ (дата обращения: 07.06.2021).
14. Dependency Injector (GitHub). [Электронный ресурс]. Режим доступа:https://github.com/ets-labs/python-dependency-injector/ (дата обращения: 07.06.2021).
15. Dependency Injector (Python Package Index). [Электронный ресурс]. Режим доступа:https://pypi.org/project/dependency-injector/ (дата обращения: 07.06.2021).
16. Dependency injection and inversion of control in Python. [Электронный ресурс]. Режим доступа: https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html/ (дата обращения: 07.06.2021).
17. Dependency injection. [Электронный ресурс]. Режим доступа: https://en.wikipedia.org/wiki/Dependency_injection/ (дата обращения: 07.06.2021).
18. Применение шаблона проектирования внедрения зависимостей для разработки интернет-приложений на основе программного комплекса Dependency Injector // НАУЧНЫЙ ЖУРНАЛ (№ 5 (60) июнь, 2021).
ПРИМЕНЕНИЕ ШАБЛОНА ПРОЕКТИРОВАНИЯ ВНЕДРЕНИЯ
ЗАВИСИМОСТЕЙ ДЛЯ РАЗРАБОТКИ ИНТЕРНЕТ-ПРИЛОЖЕНИЙ НА ОСНОВЕ ПРОГРАММНОГО КОМПЛЕКСА DEPENDENCY INJECTOR Могилатов Р.К.
Могилатов Роман Константинович - ведущий инженер, направление Python, компания СофтСерв, г. Днепр, Украина
Аннотация: данная научная работа исследует особенности и результаты подхода внедрения зависимостей для разработки программных приложений сети Интернет на базе программного комплекса Dependency Injector.
Ключевые слова: внедрение зависимостей, шаблоны проектирования, Python, программирование, разработка программного обеспечения.
Цель исследования
Данная научная работа исследует особенности и результаты подхода внедрения зависимостей для разработки программных приложений сети Интернет на базе программного комплекса Dependency Injector.
Техническое задание
Создать программное приложение для работы в сети интернет на базе программного комплекса Dependency Injector. Для разработки необходимо использовать высокоуровневый язык программирования общего назначения с динамической строгой типизацией и автоматическим управлением памятью Python.
Для разработки программного приложения для работы в сети интернет необходимо использовать прикладную задачу: необходимо создать программное обеспечение для осуществления поиска репозиториев с программным кодом на крупнейшем веб-сервисе для хостинга IT-проектов и их совместной разработки GitHub.
Функциональные требования:
- Пользователь программного приложения должен иметь возможность открыть интернет страницу и ввести поисковый запрос.
- После окончания введения поискового запроса пользователь должен иметь возможность выполнить поиск нажав клавишу Enter.
- Программное приложение должно выполнить поиск подходящих репозиториев с программным кодом на веб-сервисе Github.
- Программное приложение должно отобразить страницу результатов поиска со списком найденных репозиториев с программным кодом на интернет странице.
- Программное приложение должно отобразить исходных поисковый запрос на странице результатов поиска со списком найденных репозиториев с программным кодом на интернет странице.