Dependency Injection Vocabulary: IoC, DI Containers, and SOLID Principles (англійською)

Введення залежностей, інверсія керування, контейнери DI, введення конструкторів і словник SOLID для розробників серверів.

Якщо ви працюєте над будь- якою сучасною базою коду сервера — на Java, C#, Python або TypeScript — ви майже щодня стикаєтеся з поняттям введення залежностей. У подібних дискусіях, перегляді коду, обговоренні архітектури і коментарях щодо запитів на завантаження ви повинні знати, що означає « під’ єднати контейнер », « зареєструвати службу » або « порушити принцип інверсії залежностей ». Цей підручник містить всі необхідні терміни, визначення простою англійською мовою і приклади з реального життя, щоб ви могли безпечно стежити за обговореннями і приєднуватися до них.


Основні терміни

** Введення залежностей (DI) ** — шаблон розробки, за якого клас отримує потрібні йому об’ єкти (свої * залежності *) з зовнішнього джерела, а не створює їх самостійно. Замість написання new EmailService() всередині вашого класу, служба електронної пошти * передавалася * ззовні.

«Ми переробили платіжний модуль, щоб використовувати DI — тепер контролер не створює нічого безпосередньо»

“До того, як ми додали DI, кожен клас оновив своє власне підключення до бази даних. Тестування було кошмаром»

** Інверсія контролю (IoC) ** — ширший принцип, за яким рамка або контейнер, а не ваш власний код, має керувати потоком програми і створювати об’ єкти. Введення залежностей є одним із способів досягнення IoC.

«Вся суть IoC полягає в тому, що ваша бізнес-логіка не турбується про те, як вона отримує свої залежності — вона просто декларує те, що їй потрібно»

«Ми перейшли на IoC контейнер і раптом код запуску зменшився з 300 рядків до близько 40.»

** DI контейнер ** (також відомий як * IoC контейнер *) — компонент платформи, який автоматично створює об’ єкти, розв’ язує їх залежності і керує їх термінами служби. Прикладами є Spring (Java), вбудований в.NET IServiceCollection, та InversifyJS (TypeScript).

«Всі наші сервісні реєстрації живуть в одному місці — контейнер обробляє все звідти»

Якщо контейнер не може розв’язати залежність при запуску, він відразу ж викидає, а не мовчки відмовляється під час виконання

SOLID — акронім для п’яти принципів об’єктно-орієнтованого дизайну: Одна відповідальність, Відкритий/Закритий, Заміна Лісковим, Сегрегація інтерфейсів, і Інверсія залежностей. Останні два особливо актуальні для DI.

Команда зробила SOLID перегляд нового модуля і знайшла три класи, які робили занадто багато

«Зрозуміти SOLID — це ставки на столі для старших ролей бекенда — інтерв’юери постійно запитують про це»


Стиль ін’єкцій

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

«Ми стандартизували введення конструктора в усі сервіси — якщо вам щось потрібно, декларуйте це в конструкторі»

«Введення конструктора робить очевидним те, від чого залежить клас; вам не потрібно читати кожен метод, щоб знайти приховані залежності»

** Введення параметра setter ** — залежності надаються за допомогою відкритих методів setter після створення об’ єкта. Корисно, якщо залежність не є обов’ язковою або якщо існує циклічна залежність, яку неможливо розв’ язати під час побудови.

«Ми використовували введення сетера для опціонального журналу аудиту — якщо він не налаштований, служба все ще працює добре.»

«Ін’єкція сетера може бути ризикованою, тому що об’єкт існує в частково ініціалізованому стані між конструкцією та ін’єкцією»

** Введення властивостей ** — схоже на введення засобом встановлення, але залежності буде призначено безпосередньо до загальних властивостей, а не за допомогою спеціальних методів встановлення. Поширене в деяких фреймворках, але загалом вважається менш безпечним, ніж введення конструктора.

«Старий код використовував введення властивостей скрізь, що зробило дуже важко побачити, що насправді було потрібно»

Наш стильний посібник забороняє введення властивостей — якщо це необов’язково, використовуйте сетер; якщо це необхідно, використовуйте конструктор


Принципи та моделі проектування

Принцип інверсії залежностей — « D » в SOLID. Він стверджує, що високорівневі модулі не повинні залежати від низькорівневих модулів; обидва повинні залежати від абстракцій (інтерфейсів). Ваш OrderService повинен залежати від інтерфейсу IPaymentGateway, а не від конкретного класу StripePaymentGateway.

«Ми порушили інверсію залежностей — рівень бізнес-логіки імпортує безпосередньо з шару інфраструктури»

«Якщо ми застосували інверсію залежності, зміна постачальника платежів була просто питанням реєстрації іншої реалізації»

** Сегрегація інтерфейсів ** — « I » в SOLID. Він говорить, що жоден клієнт не повинен бути змушений залежати від методів, які він не використовує. Замість одного великого інтерфейсу, використовуйте декілька менших, зосереджених.

«Цей інтерфейс IRepository має 20 методів — більшість сервісів потребують лише трьох або чотирьох. Порушення класичної сегрегації інтерфейсу»

Ми розділили інтерфейс бога на IReadRepository і IWriteRepository і все стало набагато чистішим»

** Шаблон декоратора ** — структурний шаблон, за допомогою якого клас обгортає інший клас, що реалізує той самий інтерфейс, додаючи поведінку без зміни оригіналу. Зазвичай використовується разом з DI для додавання перетинаючих проблем, таких як ведення журналу або кешування.

«Ми додали декоратор кешування навколо сховища — контейнер вводить декоровану версію прозорою»

«Декоратор журналювання був зареєстрований в контейнері як обгортка, тому код сервісу не змінився взагалі»

** Локатор служб (анти- шаблон) ** — підхід, за якого клас викликає центральний реєстр для отримання своїх власних залежностей, замість того, щоб їх вводити. Це широко вважається анти-патерном, тому що він приховує залежності, робить тестування важчим, і парує код з самим локатором.

«Стара кодова база була повна викликів ServiceLocator.Get<T>() — ми витратили спринт, замінивши їх всіх належним DI»

«Локатор сервісів по суті є просто глобальний стан з додатковими кроками. Уникайте цього, якщо можете»

Кругова залежність — ситуація, коли клас A залежить від класу B, який залежить від класу A (безпосередньо або через ланцюг). Контейнери DI покажуть помилку або непередбачувану поведінку, якщо вони виявлять таку поведінку.

«Контейнер не працює при запуску з помилкою залежності між UserService і AuthService

«Ми вирішили кругову залежність, витягнувши спільну логіку в третю службу, від якої не залежить жодна з початкових двох»


Налаштування контейнера

** Реєстрація ** — процес повідомлення контейнеру DI, яку конкретну реалізацію використовувати, коли запитується певний інтерфейс або тип. У.NET ви можете написати services.AddScoped<IEmailService, SmtpEmailService>().

“Не забувайте додавати реєстрацію в Startup.cs, інакше контейнер викличе виняток розв’язування під час виконання.”

«Ми централізували всі реєстрації в методах розширення — один AddInfrastructure() виклик встановлює все»

** Lifetime ** — правило, яке визначає, наскільки довго буде існувати зареєстрований екземпляр служби. Три типових терміни життя:

  • ** Singleton ** — один екземпляр на весь час роботи програми.
  • ** Область дії ** — один екземпляр на запит (або на область дії).
  • ** Перехідний ** — новий екземпляр кожного разу, коли залежність буде розв’ язано.

«Ми зареєстрували контекст бази даних як об’єкт, а не як одиничне значення — одиничне значення DbContext викликає проблеми з потоками»

“Ця витік пам’яті відслідковується назад до перехідного сервісу, який має посилання на одиночну. «Життя без емоцій»

** Автоматичне підключення ** — можливість багатьох контейнерів DI, яка автоматично розв’ язує залежності конструктора за допомогою перевірки підписів типів, не вимагаючи явного налаштування залежностей. Ви реєструєте типи, контейнер визначає, як їх підключити.

«Autowiring Spring обробляє всі конструктори введення автоматично — ми просто анотувати клас з @Service

«Автообладнання зручно, але у великих проектах явна реєстрація дає вам кращу видимість того, що насправді вводиться»


Як використовувати їх у розмові

** Сценарій 1 — Коментар перегляду коду **

Колега написав службу, яка створює HttpClient всередині свого конструктора з new HttpClient().

“Це повинно використовувати DI, а не інстанціонувати HttpClient безпосередньо. Зареєструйте його в контейнері з правильним часом життя і введіть його через конструктор — таким чином ми можемо знущатися з нього в тестах і контейнер правильно управляє спільним використанням сокетів. ”

** Сценарій 2 — Обговорення архітектури **

Команда обговорює питання про те, чи додавати кешування до шару сховища.

“Замість зміни існуючого сховища, давайте застосуємо шаблон декоратора. Ми обгортаємо ProductRepository з CachedProductRepository, що реалізує той же інтерфейс IProductRepository, а потім оновлює реєстрацію контейнера. Сервісний шар не буде знати або турбуватися»

Сценарий 3 - Инцидент после смерти

Вада виробництва, спричинена контекстом бази даних, який було захоплено у фоновому робітнику singleton.

«Головною причиною було невідповідність життя — фонова служба зареєстрована як синглет, але вона розв’язувала обсяг DbContext безпосередньо. Область служби не повинна бути використана одиночними користувачами. Нам потрібно використовувати IServiceScopeFactory, щоб створити явний обсяг всередині worker. ”

** Сценарій 4 — Введення нового розробника **

Пояснення, чому вилучається старий код локалізації служб.

«Ви побачите деякі виклики Locator.Resolve<T>() в застарілих модулях — ми мігруємо їх до введення конструктора. Проблема з локалізатором служб полягає у тому, що залежності приховано; ви не зможете дізнатися, що потрібно класу, лише подивившись на його конструктор. Введення конструктора робить контракт явним і клас набагато легше перевірити»


Швидка реакція

TermShort definition
Dependency injection (DI)Passing dependencies into a class rather than letting it create them
Inversion of control (IoC)Framework controls object creation and flow, not your code
DI containerFramework component that creates and wires objects automatically
Constructor injectionDependencies passed through the constructor — preferred style
Dependency inversion principleDepend on abstractions (interfaces), not concrete implementations
RegistrationMapping an interface to its concrete implementation in the container
LifetimeHow long a service instance lives: singleton / scoped / transient
Decorator patternWrapping a class to add behaviour without changing the original
Service locatorAnti-pattern: class fetches its own dependencies from a registry
Circular dependencyA → B → A dependency chain that breaks the container

Поширені запитання

Про що ця стаття "Dependency Injection Vocabulary: IoC, DI Containers, and SOLID Principles (англійською)"?

Введення залежностей, інверсія керування, контейнери DI, введення конструкторів і словник SOLID для розробників серверів.

Чи безкоштовна ця стаття?

Так. Усі статті на CoderSlingo, включно з цією, доступні безкоштовно без реєстрації.

Скільки часу займає читання "Dependency Injection Vocabulary: IoC, DI Containers, and SOLID Principles (англійською)"?

Приблизно 9 min.