I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: для побудови архітектури додатків немає "найкращого" чи "найправильнішого" підходу. Це дуже творча робота. Ви завжди повинні вибирати найпростішу та розширювану архітектуру, що буде зрозуміло будь-якому розробнику, який починає працювати над вашим проектом, або іншим розробникам у вашій команді, але я згоден, що тут може бути "хороший" і "поганий" "архітектура.
Ви сказали: collect the most interesting approaches from experienced iOS developers
я не вважаю, що мій підхід є найцікавішим або правильним, але я його використав у кількох проектах і задоволений ним. Це гібридний підхід із тих, про кого ви згадали вище, а також із вдосконаленнями моїх власних досліджень. Мені цікаві проблеми побудови підходів, які поєднують кілька відомих закономірностей та ідіом. Я думаю, що багато моделей підприємств Фоулера можна успішно застосувати до мобільних додатків. Ось перелік найцікавіших з них, які ми можемо застосувати для створення архітектури додатків для iOS ( на мою думку ): Службовий шар , блок роботи , віддалений фасад , об’єкт передачі даних ,Шлюз , супертип типу шару , особливий кейс , модель домену . Ви завжди повинні правильно розробити шар моделі та не забувати про стійкість (це може значно підвищити ефективність вашої програми). Ви можете використовувати Core Data
для цього. Але не слід забувати, що Core Data
це не ОРМ чи база даних, а керований графік об'єктів із наполегливістю як хороший варіант цього. Отже, дуже часто це Core Data
може бути занадто важко для ваших потреб, і ви можете подивитися на нові рішення, такі як Realm і Couchbase Lite , або створити власний легкий об’єктний шар відображення / стійкість на основі сирого SQLite або LevelDB. Також я раджу ознайомитись із дизайном, керованим доменом та CQRS .
Спочатку, я думаю, ми повинні створити ще один шар для створення мереж, тому що ми не хочемо жирових контролерів або важких переповнених моделей. Я не вірю в ці fat model, skinny controller
речі. Але я вірю в skinny everything
підхід, тому що жоден клас не повинен бути жирним ніколи. Всі мережі можна взагалі абстрагувати як бізнес-логіку, отже, у нас повинен бути ще один шар, де ми можемо це розмістити. Сервісний шар - це те, що нам потрібно:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
У нашій MVC
царині Service Layer
це щось на зразок посередника між доменною моделлю та контролерами. Існує досить схожа варіація цього підходу під назвою MVCS, де a Store
- насправді наш Service
шар. Store
Продає екземпляри моделей і обробляє мережу, кешування тощо. Хочу зазначити, що ви не повинні писати всю свою мережеву та ділову логіку на своєму сервісному рівні. Це також може розглядатися як погана конструкція. Для отримання додаткової інформації дивіться моделі Anemic and Rich домена. Деякі методи обслуговування та бізнес-логіку можна обробляти в моделі, тому це буде «багата» (з поведінкою) модель.
Я завжди широко використовую дві бібліотеки: AFNetworking 2.0 та ReactiveCocoa . Я думаю, що це необхідно для будь-якого сучасного додатку, який взаємодіє з мережею та веб-сервісами або містить складну логіку інтерфейсу.
АРХИТЕКТУРА
Спочатку я створюю загальний APIClient
клас, який є підкласом AFHTTPSessionManager . Це робочий коник усіх мереж у додатку: усі класи сервісів делегують йому фактичні REST-запити. Він містить усі налаштування клієнта HTTP, які мені потрібні в конкретній програмі: фіксація SSL, обробка помилок та створення простих NSError
об'єктів з детальними причинами відмов та описом всіх API
та помилок підключення (у такому випадку контролер зможе показувати правильні повідомлення для користувача), налаштування серіалізаторів запитів та відповідей, заголовків http та інших матеріалів, пов’язаних із мережею. Тоді я логічно розділити всі запити API в подсервіси або, вірніше, microservices : UserSerivces
, CommonServices
, SecurityServices
,FriendsServices
тощо, відповідно до бізнес-логіки, яку вони реалізують. Кожен з цих мікросервісів - окремий клас. Вони разом утворюють а Service Layer
. Ці класи містять методи для кожного запиту API, моделей доменних процесів і завжди повертає a RACSignal
з проаналізованою моделлю відповіді або NSError
абоненту.
Хочу зазначити, що якщо у вас є складна логіка серіалізації моделі - тоді створіть для неї ще один шар: щось на зразок Data Mapper, але більш загальне, наприклад, JSON / XML -> Model mapper. Якщо у вас є кеш: тоді також створіть його як окремий шар / службу (не слід змішувати бізнес-логіку з кешуванням). Чому? Тому що правильний кеширующий шар може бути досить складним із власними дітками. Люди реалізують складну логіку, щоб отримати дійсне, передбачуване кешування, як-от, наприклад, моноїдне кешування з проекціями на основі профункціонерів. Ви можете прочитати про цю прекрасну бібліотеку під назвою Карлос, щоб зрозуміти більше. І не забувайте, що основні дані дійсно можуть допомогти вам у вирішенні всіх проблем кешування і дозволять писати менше логіки. Крім того, якщо у вас є певна логіка між NSManagedObjectContext
моделями та запитами сервера, ви можете використовуватиШаблон сховища , який відокремлює логіку, яка отримує дані, і відображає їх в модель сутності від бізнес-логіки, яка діє на модель. Отже, я раджу використовувати шаблон репозиторію, навіть якщо у вас є архітектура на основі основних даних. Repository тазів абстрактні речі, як NSFetchRequest
, NSEntityDescription
, NSPredicate
і так далі для простих методів , таких як get
або put
.
Після всіх цих дій на рівні Сервісу абонент (контролер перегляду) може виконати деякі складні асинхронні речі з відповіддю: маніпуляції сигналом, ланцюжок, відображення карти тощо за допомогою ReactiveCocoa
примітивів, або просто підписатися на нього та показати результати у поданні . Я впорснути з Injection Dependency у всіх цих службах класів моїх APIClient
, яка переведе конкретний виклик служби у відповідному GET
, POST
, PUT
, DELETE
і т.д. запит на REST кінцевої точку. У цьому випадку APIClient
передається неявно всім контролерам, ви можете зробити це явним шляхом параметризованим на рівні APIClient
класів обслуговування. Це може мати сенс, якщо ви хочете використовувати різні налаштуванняAPIClient
для конкретних класів обслуговування, але якщо ви з якихось причин не хочете зайвих копій або ви впевнені, що завжди будете використовувати один конкретний екземпляр (без налаштувань) APIClient
- зробіть це однократним, але НЕ, будь ласка, DON 'Зробіть класи обслуговування як одиночні.
Потім кожен контролер перегляду знову разом із DI вводить необхідний йому клас обслуговування, викликає відповідні методи обслуговування та складає їх результати з логікою інтерфейсу користувача. Для введення залежності я люблю використовувати BloodMagic або більш потужний фреймворк Тайфун . Я ніколи не використовую одиночних APIManagerWhatever
класів, класів Бога чи інших неправильних речей. Тому що якщо ви телефонуєте своєму класу WhateverManager
, це означає, що ви не знаєте його призначення, і це поганий вибір дизайну . Синглтонтон також є антидіаграном, і в більшості випадків (крім рідкісних) є неправильним рішенням. Синглтон слід розглядати лише в тому випадку, якщо всі три наступні критерії задоволені:
- Право власності на єдиний екземпляр не може бути розумно присвоєний;
- Бажана лінива ініціалізація;
- Глобальний доступ не передбачений інакше.
У нашому випадку право власності на один примірник не є проблемою, а також нам не потрібен глобальний доступ після того, як ми розділили нашого менеджера богів на сервіси, тому що зараз лише один або кілька виділених контролерів потребують певної послуги (наприклад, UserProfile
потреби контролера UserServices
тощо) .
Ми завжди повинні дотримуватися S
принципу в SOLID і використовувати розділення проблем , тому не ставте всі свої методи обслуговування та мережеві дзвінки в один клас, тому що це божевільно, особливо якщо ви розробляєте велику корпоративну програму. Ось чому ми повинні розглянути питання щодо введення залежності та надання послуг. Я вважаю такий підхід сучасним та пост-ОО . У цьому випадку ми розділили наш додаток на дві частини: логіка управління (контролери та події) та параметри.
Одним із типів параметрів були б звичайні параметри даних. Це те, що ми обходимо функції, маніпулюємо, модифікуємо, зберігаємося тощо. Це сутності, агрегати, колекції, класи кейсів. Іншим видом були б параметри "обслуговування". Це класи, які інкапсулюють бізнес-логіку, дозволяють спілкуватися із зовнішніми системами, забезпечують доступ до даних.
Ось загальний робочий процес моєї архітектури на прикладі. Припустимо, у нас є FriendsViewController
список, який відображає список друзів користувача, і ми маємо можливість видалити з друзів. Я створюю у своєму FriendsServices
класі метод під назвою:
- (RACSignal *)removeFriend:(Friend * const)friend
де Friend
модель / доменний об’єкт (або він може бути просто User
об’єктом, якщо вони мають подібні атрибути). Underhood цього методу розборів Friend
до NSDictionary
параметрів JSON friend_id
, name
, surname
, friend_request_id
і так далі. Я завжди використовую бібліотеку Mantle для подібного шаблону і для мого шару моделі (розбір назад і вперед, управління вкладеними ієрархіями об'єктів у JSON тощо). Після розбору він викликає APIClient
DELETE
метод , щоб зробити фактичний запит REST і повертається Response
в RACSignal
викликає програмі ( FriendsViewController
в нашому випадку) , щоб відобразити відповідне повідомлення для користувача або будь-який інший .
Якщо наш додаток дуже великий, ми повинні розділити свою логіку ще зрозуміліше. Наприклад, не завжди добре змішувати Repository
або моделювати логіку з Service
однією. Коли я описав свій підхід, я сказав, що removeFriend
метод повинен бути в Service
шарі, але якщо ми будемо більш педантичними, ми можемо помітити, що він краще належить Repository
. Давайте згадаємо, що таке сховище. Ерік Еванс дав це точний опис у своїй книзі [DDD]:
Репозиторій представляє всі об'єкти певного типу у вигляді концептуального набору. Він діє як колекція, за винятком більш детальної можливості запиту.
Отже, a Repository
по суті є фасадом, який використовує семантику стилю колекції (Додати, оновити, видалити) для забезпечення доступу до даних / об’єктів. Ось чому , коли у вас є що - щось на кшталт: getFriendsList
, getUserGroups
, removeFriend
ви можете помістити його в Repository
, оскільки збір подібної семантиці досить ясно тут. І такий код:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
це безумовно логіка бізнесу, оскільки вона виходить за рамки основних CRUD
операцій і з'єднує два доменні об'єкти ( Friend
і Request
), тому її слід розміщувати в Service
шарі. Також хочу зазначити: не створюйте зайвих абстракцій . Використовуйте всі ці підходи з розумом. Тому що, якщо ви будете переповнювати свою програму абстракціями, це збільшить її випадкову складність, а складність спричинить більше проблем у програмних системах, ніж будь-що інше
Я описую вам "старий" приклад Objective-C, але цей підхід може бути дуже легко адаптований для мови Swift з набагато більшими вдосконаленнями, оскільки він має більше корисних функцій та функціональний цукор. Дуже рекомендую використовувати цю бібліотеку: Moya . Це дозволяє створити більш елегантний APIClient
шар (наш робочий коник, як ви пам’ятаєте). Тепер нашим APIClient
провайдером буде тип значень (enum) з розширеннями, що відповідають протоколам, та використовуючи відповідність зразків руйнування. Swift enums + узгодження шаблонів дозволяє нам створювати алгебраїчні типи даних, як у класичному функціональному програмуванні. Наші мікросервіси будуть використовувати цього вдосконаленого APIClient
постачальника, як у звичайному підході Objective-C. Для шару моделі замість Mantle
вас можна використовувати бібліотеку ObjectMapperабо мені подобається використовувати більш елегантну та функціональну бібліотеку Argo .
Отже, я описав мій загальний архітектурний підхід, який можна пристосувати для будь-якого застосування. Звичайно, може бути набагато більше поліпшень. Раджу вам вивчити функціональне програмування, тому що ви можете отримати від цього багато користі, але не надто перешкоджайте цьому. Усунення надмірного, загального, глобального стану, що змінюється, створення непорушної моделі домену або створення чистих функцій без зовнішніх побічних ефектів, як правило, є хорошою практикою, і нова Swift
мова заохочує це. Але завжди пам’ятайте, що перевантаження вашого коду важкими чистими функціональними зразками, категорично-теоретичні підходи - погана ідея, оскільки інші розробники будуть читати та підтримувати ваш код, і вони можуть бути розчаровані чи страшніprismatic profunctors
і такі речі у вашій незмінній моделі. Те ж саме ReactiveCocoa
: не вдавайте RACify
свій код занадто багато , оскільки він може стати нечитабельним дуже швидко, особливо для новачків. Використовуйте його, коли це дійсно може спростити ваші цілі та логіку.
Так, read a lot, mix, experiment, and try to pick up the best from different architectural approaches
. Це найкраща порада, яку я можу дати вам.