Наша совместная команда Banwar.org

Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

Архітектура додатків: концептуальні шари і домовленості щодо їх використання

  1. приклад домовленостей
  2. Трішки історії
  3. Первинні правила і домовленості щодо іменування
  4. Абстрактні рівні Data Access Layer
  5. Repository
  6. Provider
  7. Manager
  8. Controller
  9. Service
  10. висновок

ru-RU | створено: 03.02.2017 | опубліковано: 03.02.2017 | оновлено: 23.03.2019 | переглядів за весь час: 4955

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

приклад домовленостей

Розберемо для прикладу загальновідомий фреймворк ASP.NET MVC . Якщо ви працювали з цією системою, то напевно звернули увагу на те, що в проекті, який створюється Visual Studio за замовчуванням присутні папки Models, Views, Controllers. Це перша домовленість, що кожен тип файлу (класу) лежить у своїй власній папці.

У перших версіях фреймворку ASP.NET MVC контролери могли лежати тільки в папці Controllers. Пізніше це обмеження було зняте.

Eсли при розробки будь-якого методу контролера ви спробуєте створити метод і відкрити його, не створивши уявлення (View) цього методу, то система видасть вам повідомлення про помилку:

Зверніть увагу на те, де система спробувала знайти "втрачене" уявлення, перш ніж видати повідомлення. Така поведінка також визначено домовленостями фреймворка. В даному випадку, правило говорить, що якщо подання (View), не знайдено в папці контролера Home, то слід її пошукати спочатку в папці Shared, потім все те ж саме, але вже з іншим розширенням для іншого движка рендеринга (при наявності підключених декількох двигунів рендеринга розмітки). Насправді домовленостей подібного роду в ASP.NET MVC дуже велика кількість. І тим більше ви їх будете зустрічати, чим глибше будете занурюватися в "нетрі" фреймворка.

ASP.NET MVC фреймворк як і всі фреймворки, у всякому разі в більшості своїй, побудовані на основі парадигми " угоди по конфігрураціі "(Configuration over convention).

принцип угоди по конфігрураціі як правило, застосовується в розробці фреймворків , І дозволяє скоротити кількість необхідної конфігурації без втрати гнучкості.

Суть даної статті показати, що використання згаданої вище парадигми стосовно колективної розробки, може істотно прискорити (спростити) процес розробки додатків, особливо якщо в розробці бере участь велика кількість розробників. "Угоди по конфігурації" дозволяють вирішити питання, на які найчастіше витрачаються дорогоцінні хвилини, години і навіть дні і тижні. Наприклад, часто виникає питання, як правильно назвати створюваний клас, властивість, метод, проект, змінну, рішення (solution)? Скільки розробників, стільки і варіантів, якщо не домовиться заздалегідь про правила іменування. А якщо в колектив прийшов новий розробник, то як він може почати писати коректний код, не знаючи про правила, за якими цей код пишеться? Безумовно, існують величезна кількість допоміжних утиліт (наприклад, StyleCop ), Які спрощують задачу, а й вони бувають безсилі в деяких випадках.

Треба визнати, що з кожним роком завдання для розробників ускладнюються через складність завдань, які вирішує бізнес. Більш складні завдання вимагають більш комплексних підходів у їх вирішенні. Найчастіше буває недостатньо створити додаток (програму, систему), зазвичай потрібно її підтримувати і розвивати після випуску остаточної версії. І якщо у вашому колективі існують подібні правила і домовленості, вам буде набагато простіше знайти місце в коді, правильний контролер і метод. Домовленості і правила іменування допомагають швидше орієнтуватися в коді.

Трішки історії

Як приклад наростання складності, приведу еволюцію патернів Business Logic, які описують, як і де дожна бути релизована бізнес-логіка. Патерни для організації бізнес-уровеня (бізнес-логіка) з плином часу зазнали істотних змін, від простого до складного. Більш того, на цьому еволюція не зупинилася.

Кожен з них докладно описав Martin Fowler , Тому я не буду на них зупинятися детально. Еволюція говорить про те, що програмувати системи, які маніпулюють даними стає все складніше. Треба розуміти, що використання патернів проектування для побудови архітектури складних систем істотно спрощує подальшу підтримку системи (maintainability) і введення нового функціоналу, але при цьому розробка сильно ускладнюється. Для спрощення цієї самої розробки, я і пропоную використовувати домовленості правила, тобто "угоди по конфігурації" але стосовно до процесу написання коду.

Що говорити, якщо вже не тільки серед розробників, але і серед замовників все частіше і частіше лунають подібні слова і фрази: ORM, Repository, Business Layer, Abstraction і Dependency Injection, MVVM і інші.

Серед розробників ходять давні суперечки щодо того, чи слід використовувати додаткову "прошарок" типу ORM між базами даних і DAL. Наприклад, на sql.ru або на habrahabr.ru частенько піднімаються теми. Лейтмотивом в подібних суперечках звучить думка: "... для складних проектів використання ORM істотно скорочує рутину ...".

Так повелося, що з деякого часу я перестав розділяти проекти на складні і прості, взявши за основу використання ORM для проектів будь-якої складності. Особливо якщо врахувати, що ORM дозволяє використовувати підхід CodeFirst (зокрема, EntityFramework ).

Насмілюся припустити, що ви вже знайомі з паттернами проектування , І зокрема, з паттернами Repository і UnitOfWork , Саме на їх основі я і буду говорити про домовленості для побудови рівня доступу до даних (Data Access Layer). А також припущу, що ви маєте уявлення про Dependency Injection .

Використання патернів не дає гарантії, що ваш код буде написаний правильно.

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

Власне кажучи, далі мова піде про домовленості, які були вироблені дослідним шляхом за досить тривалий термін колективної розробки. Хочеться вірити, що вони комусь допоможуть в роботі над складними проектами. І було б просто чудово, якби ви поділилися в коментарях своїми власними правилами і домовленостями, якими користуєтеся ви і ваша команда.

Не втомлююся повторювати фразу "все вже придумано за нас", повторю її і в даному контексті. У світі розробки вже існують домовленості про іменування, наприклад від компанія Microsoft або ті, інший варіант в Wikipedia . За основу для своїх правил я взяв домовленості від Microsoft. Тож почнемо.

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

✓ Назва будь-якого проекту має починатися з {CompanyName}. Дане правило актуально для розробників компанії {CompanyName}.

✓ Після першого слова {CompanyName} через точку повинно бути вказано ім'я проекту.

✓ За назвою проекту обов'язково повинна слідувати назва платформи.

✓ Після зазначеної платформи частини внутрішньої архітектури проекту.

X Забороняється використовувати знаки підкреслення, дефіси та інші символи.

X Забороняється використовувати скорочення і загальновідомі абревіатури

X Забороняється використовувати цифри в найменуваннях.

X Рекомендується уникати використання складно складених назв в зазначених частинах іменування

Правила іменування змінних:

✓ Використовуйте легко читаються імена ідентифікаторів. Наприклад, властивість з ім'ям HorizontalAlignment є більш зрозумілим, ніж AlignmentHorizontal.

✓ Читабельність важливіше стислості. Ім'я властивості CanScrollHorizontally краще, ніж ScrollableX (непрозорими посилання на вісь x).

X Забороняється використовувати знаки підкреслення, дефіси та інші символи.

X Забороняється використовувати угорську нотацію.

X Уникайте використання імен ідентифікаторів, конфліктуючих з ключовими словами широко використовуваних мов програмування.

X Не використовуйте абревіатури або скорочення як частина імені ідентифікатора. Наприклад, використовуйте GetWindow замість GetWin.

X Забороняється використовувати акроніми, які не є загальноприйнятим і навіть в тому випадку, якщо вони знаходяться, тільки при необхідності.

✓ Використовуйте семантично значущі імена замість зарезервованих слів мови для імен типів. Наприклад GetLength є ім'ям краще, ніж GetInt.

✓ Використовуйте ім'я універсального типу CLR, а не ім'я конкретного мови, в рідкісних випадках, коли ідентифікатор не має семантичного значення за межами свого типу. Наприклад, перетворення в метод Int64 повинен бути названий ToInt64, а не ToLong (оскільки Int64 - це ім'я середовища CLR для C # -псевдонім long). У наступній таблиці представлені деякі базові типи даних за допомогою імен типів середовища CLR (а також відповідні імена типів для C #, Visual Basic і C ++).

✓ Використовуйте звичайні імена, таких як value або item, замість того щоб повторення імені типу в рідкісних випадках, коли ідентифікатор не має семантичного значення і тип параметра не має значення.

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

{CompanyName} .ProjectForManagingContent

{CompanyName} .Project.API.V1

{CompanyName} .ProjectForManaging

{CompanyName} .ManagingSystem

{COMPANYNAME} .WEB.PORTAL

MVC.Site.Utils. {CompanyName}

{CompanyName} .Client.System

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

Для прикладу ім'ям рішення возмем неіснуючий сайт http://project.company.ru. Проект порталу на платформі ASP.NET MVC повинен мати наступні простір імен.

Назва рішення (solution):

{CompanyName} .Project

Назви проектів (projects) за типом приналежності до рівня абстракції:

{CompanyName} .Project.Contracts

{CompanyName} .Project.Models

{CompanyName} .Project.Data

{CompanyName} .Project.Globalization

{CompanyName} .Project.Utils

{CompanyName} .Project.Android

{CompanyName} .Project.MacOS

{CompanyName} .Project.UWP

{CompanyName} .Project.WebAPI

{CompanyName} .Project.WPF

Назви проектів (projects) при використанні Unit-тестування:

{CompanyName} .Project.Contracts.Tests

{CompanyName} .Project.Models.Tests

{CompanyName} .Project.Data.Tests

{CompanyName} .Project.Globalization.Tests

{CompanyName} .Project.Utils.Tests

{CompanyName} .Project.Android.Tests

{CompanyName} .Project.MacOS.Tests

{CompanyName} .Project.UWP.Tests

{CompanyName} .Project.API.Tests

{CompanyName} .Project.WPF.Tests

Абстрактні рівні Data Access Layer

Власне кажучи, ми підійшли до ключового моменту статті. Я буду наводити приклади стосовно платформі ASP.NET і, в часності, до проекту MVC 5, однак, прийміть до відома, що нічого не заважає використовувати дану концепцію в WPF-додатку або в якому-небудь ще, наприклад, JavaScript-додатки на основі Single Page Application (SPA).

Отже, в своїх проектів я використовую такі рівні абстракції для рівня бізнес-логіки:

Отже, в своїх проектів я використовую такі рівні абстракції для рівня бізнес-логіки:

Це базові поняття для більшості проектів, але треба зізнатися, що були проекти, для яких доводилося додавати рівні вище над рівнем Manager. Тепер про все по порядку. Давайте для більшої наочності візьмемо конкретні сутності і зробимо для них обгортку з класів для бізнес-рівня. Припустимо, що у нас в проекті є сутності Category, Post, Tag, Comment. Схоже на сутності для реалізації блогу? Так воно і є.

Repository

Сподіваюся, ви прочитали опис на зображенні для Repository, лише поясню, що базові операції це: читання з бази списку, читання з бази однієї сутності за ідентифікатором, вставка нового запису в базу, редагування існуючої в базі записи, видалення запису. Деякі Repository можуть мати додаткові методи, наприклад, метод Clear для сховища LogRepository. Отже, для кожної із зазначених сутностей створюємо свій репозиторій: CategoryRepository, PostRepository, TagRepository, CommentRepository.

Правило іменування для Repository: назва суті в однині перед словом Repository.

У мене є ReadableRepositoryBase <T> клас, який реалізує методи PagedList <T> () і GetById (). Такому чином, успадкованих від цього класу, репозиторій спадкоємець набуває можливість отримувати запис за ідентифікатором і колекцію розбиту на сторінки. А також якщо спадкоємець від ReadableRepositoryBase <T>, який називається WritableRepositoryBase <T> і має методи Create, Update, Delete. Конкретну реалізацію я наводити не буду, бо це не є мета моєї статті, але якщо комусь буде потрібно, відпишіться в коментарях.

Такий підхід дає мені можливість успадковуватися від одного або від другого абстрактного класу, реалізуючи рольової доступ до суті на етапі розробки. Отже, у нас вже є чотири класи, які можуть керувати (CRUD) кожен своєю сутністю.

Provider

Далі в ієрархії рівнів бізнес-логіки доступу до даних йде Provider. Говорячи про нього, наведу приклад взаємозв'язку між сутностями Tag і Post. Припустимо, що мітка (tag) пов'язана із записом блогу (post) щодо "багато-до-багатьох" і мітки для запису блогу обов'язкові. Так ось, виходить, що при додаванні запису блогу, мені доведеться якимось чином обробляти мітки (tags). Даний функціонал підпадає під патерн Unit of Work, який управляє централізовано декількома сутностями. Таким чином, PostProvider частково реалізує патерн Unit of Work .

Правило іменування для Provider: назва суті вказується в однині перед словом Provider.

Провайдери через вливання залежностей приймають в конструктор Repository. Тобто провайдери управляють репозиторіями. Таким чином, ми підійшли до першого правилом Dependency Injection для рівнів Data Access Layer.

Правило Dependency Injection: В конструктори класів Provider можуть вливатися як залежності об'єкти Repository.

Забув згадати, що в класи Repository повинні вливатися DbContext (EntityFramework) або його абстрактний представник.

Правило Dependency Injection: В класи Repository можуть вливатися DbContext (EntityFramework) або абстракція на цей клас.

Manager

Наступним на черзі рівень Manager. Менеджери служать для управління пов'язаними сутностями (в тому числі і провайдерами), але на більш високому рівні абстракції. Припустимо, що мені потрібно змінити видимість категорії, яку я хочу приховати. Але приховуючи категорію, я повинен приховати і записи в цій категорії. Вирішуючи поставлене завдання, я створю клас CategoryManager, в якому створю метод SetVisibilityForCategory. У класі PostProvider я створю метод SetVisibilityForPostsByCategoryId (int categoryId), який буде використовувати клас PostRepository встановлювати властивість IsVisisble у всіх записів блогу належить обраної категорії. Тобто, просто змінювати значення одного властивості, а це звичайна операція поновлення (update).

Правило Dependency Injection: В класи Manager можуть вливатися як залежності об'єкти Provider.

Ключовим моментом в даному прикладі є те, що кожен з описаних рівнів абстракції реалізує свій власний функціонал, який підлягає розширенню, якщо буде потрібно. Будується, свого роду піраміда, по управлінню сутностями. Від більш "дрібних" операцій в Repository до більш "великим" в Manager. Причому "розмір" операції залежить від кількості сутностей, які вона зачіпає.

Controller

Говорячи про ASP.NET MVC не можна не згадати про контролерів, які є основою в фреймворку, навколо яких і побудована вся інфраструктура. У MVC-контролер (API-контролер теж) можуть вливатися залежності типу Repository, Provider, Manager.

Правило Dependency Injection: В класи Manager можуть також вливатися як залежності об'єкти Repository.

Хочу зауважити, що в большестве випадків, операції, які ви будете викликати з контролера повинні находітcя на рівні Provider і Manager, тому що ці два рівня спільно реалізують Unit of Work, а операції з Repository часто не беруть участь в бізнес-процесах "великого" додатка .

Правило іменування для Controller: назва суті вказується у множині перед словом Controller.

Service

Варто також згадати, що зазвичай в додатках існують бізнес-процеси, які не прив'язані до конкретної сутності. Наприклад, при формуванні замовлення на покупку товару, потрібно відправити повідомлення менеджеру або повідомлення в бухгалтерію по електронній пошті. Для реалізації такого функціоналу я створюю EmailService, який реалізує відправку повідомлення по електронній пошті.

Правило Dependency Injection: В класи Repository, Provider, Manager можуть вливатися об'єкти Service.

Таким чином, для кожного з перерахованих рівнів (Repository, Provider, Manager) може виникнути потреба ін'єкції будь-якого сервісу. Хорошим прикладом може слугувати LogService. Протоколювання (logging) доречно на будь-якому з рівнів логіки. У Repository запис в журнал про те, що запис створена. Provider може записати дані про додавання нових міток при додаванні запису в блог. Manager може записати інформацію про те, що при приховуванні категорії, також приховані і всі записи в цій категорії. Загалом, все добре в міру.

висновок

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

Варто також відзначити, що три рівня абстракції дають широкі можливості для вибору місця реалізації методів бізнес-процесів, так би мовити, розподілена бізнес-логіка. А ось рішення на якому рівні реалізовувати конкретний бізнес-процес вам вже треба буде розв'язати самостійно, бо все залежить від завдань, які ви перед собою ставите. Але в крайньому випадку можна запитати і у архітектора системи. :)

Відео за цією статтею є на каналі в youtube

Наприклад, часто виникає питання, як правильно назвати створюваний клас, властивість, метод, проект, змінну, рішення (solution)?
А якщо в колектив прийшов новий розробник, то як він може почати писати коректний код, не знаючи про правила, за якими цей код пишеться?
Схоже на сутності для реалізації блогу?

Новости

Banwar.org
Наша совместная команда Banwar.org. Сайт казино "Пари Матч" теперь доступен для всех желающих, жаждущих волнения и азартных приключений.