Чистая Архитектура для веб-приложений
Хочу поделиться с вами подходом который я уже много лет использую в разработке приложений, в том числе и веб-приложений. Многим разработчикам настольных, серверных и мобильных приложений этот подход хорошо знаком, т.к. является фундаментальным при построении таких приложений, однако в вебе он представлен очень скудно, хотя желающие использовать такой подход однозначно есть. Кроме того на таком подходе написан редактор VS Code.
В результате применения этого подхода вы отвяжетесь от конкретного фреймворка. Сможете легко переключать библиотеку представления внутри вашего приложения, например React, Preact, Vue, Mithril без переписывания бизнес логики, а в большинстве случаев даже вьюхи. Если у вас есть приложение на Angular 1, вы без проблем сможете перевести его на Angular 2+, React, Svelte, WebComponents или даже свою библиотеку представления. Если у вас есть приложение на Angular 2+, но нету специалистов для него, то вы без проблем сможете перевести приложение на более популярную библиотеку без переписывания бизнес логики. А в итоге вообще забыть про проблему миграции с фремворка на фреймворк. Что же это за магия такая?
Что такое Чистая Архитектура
Для того что бы понять это, лучше всего прочитать книгу Мартина Роберта «Чистая Архитектура» (Robert C.Martin «Clean Architecture»). Краткая выдержка из которого приведена в статье по ссылке.
Основные идеи заложенные в архитектуру:
Достигается такая гибкость за счет разделения приложения на слои Service, Repository, Model. Я же добавил к Чистой Архитектуре подход MVC и получил следующие слои:
Кому подойдет Чистая Архитектура
Веб разработка прошла большой путь развития, начиная от простого скриптования на jquery до разработки больших SPA приложений. И сейчас веб приложения стали настолько большими что количество бизнес логики стало сопоставимо или даже превосходить серверные, настольные и мобильные приложения.
Разработчикам которые пишут сложные и большие приложения, а также переносят бизнес логику с сервера на веб приложения для экономии на стоимости серверов, Чистая Архитектура поможет организовать код и отмасштабироваться без проблем до огромных масштабов.
В тоже время если ваша задача просто верстка и анимация лендингов, то Чистую Архитектуру просто некуда вставить. Если ваша бизнес логика находиться на бекенде и ваша задача получить данные, отобразить клиенту и обработать клик по кнопке, то вы не прочувствуете гибкости Чистой Архитектуры, но она может стать отличным плацдармом для взрывного роста приложения.
Где уже применяется?
Чистая архитектура не привязана к какому то конкретному фреймворку, платформе или языку программирования. Десятилетия ее используют для написания настольных приложений. Его эталонную реализацию можно найти во фреймворках для серверных приложений Asp.Net Core, Java Spring и NestJS. Так же она очень популярна при написании iOs и Android приложений. Но в веб разработке он предстал в крайне неудачном виде во фреймворках Angular.
Так как я сам не только Typescript, но и C# разработчик, то для примера возьму эталонную реализацию этой архитектуры для Asp.Net Core.
Вот упрощенный пример приложения:
Если вы не понимаете что в нем написано ничего страшного, дальше мы разберем его по частям каждый фрагмент.
Пример приведен для Asp.Net Core приложения, но для Java Spring, WinForms, Android, React архитектура и код будут такие же, меняется только язык и работа с вьюхой (если она есть).
Применение в веб-приложениях
Единственный фреймворк который пытался использовать Чистую архитектуру был Angular. Но получилось это просто ужасно, что в 1, что в 2+.
И причин для этого много:
Начинаем создавать приложение
Рассматривать Чистую Архитектуру будем на примере выдуманного приложения, максимально приближенного к реальному веб-приложению. Это кабинет в страховой компании, который отображает профиль пользователя, страховые случаи, предлагаемые тарифы страхования и инструменты для работы с этими данными.
В примере будет реализована лишь малая часть функционала, но по ней можно понять где и как располагать остальной функционал. Начинать создавать приложение начнем со слоя Controller, а View слой подключим в самом конце. А по ходу создания рассмотрим каждый слой детальнее.
Паттерн Controller
Controller — отвечает за взаимодействие пользователя с приложением. Это может быть клик по кнопке на веб странице, настольном приложении, мобильном приложении, или ввод команды в консоли линукса, или сетевой запрос, или любое другое IO событие приходящее в приложение.
Самый простой контроллер в чистой архитектуре выглядит следующим образом:
Его задача получить от пользователя событие и запустить бизнес процессы. В идеальном случае Controller ничего не знает про View, и тогда его можно переиспользовать между платформами, например Web, React-Native или Electron.
А теперь давайте напишем контроллер для нашего приложения. Его задача получить профиль пользователя, имеющиеся тарифы и предложить наилучший тариф пользователю:
У нас получился обычной контроллер без чистой архитектуры, если отнаследовать его от React.Component то получим рабочий компонент с логикой. Так пишут очень много разработчиков веб-приложений, но у такого подхода есть много существенных недостатков. Главный из которых невозможность переиспользования логики между компонентами. Ведь рекомендуемый тариф может выводиться не только в личном кабинете, но и на лендинге и множестве других мест для привлечения клиента к услуге.
Для того что бы иметь возможность переиспользовать логику между компонентами ее необходимо вынести в специальный слой, который называется Service.
Паттерн Service
Service — отвечает за всю бизнес логику приложения. Если Controller’у понадобилось получить, обработать, отправить какие то данные — он делает это через Service. Если нескольким контроллерам понадобилась одна и та же логика, они работают с Service. Но сам слой Service ничего не должен знать о слое Controller и View и окружении в котором он работает.
Давайте вынесем логику из контроллера в сервис и внедрим сервис в контроллер:
Теперь если нескольким контроллерам понадобиться получить профиль пользователя или тарифы, они смогу переиспользовать одну и туже логику из сервисов. В сервисах главное не забывать про SOLID принципы и что каждый сервис отвечает за свою зону ответственности. В данном случае один сервис отвечает за работу с профилем пользователя, а другой сервис за работу с тарифами.
Но что делать если источник данных поменяется, например fetch может смениться на websocket или grps или базу данных, а реальные данные понадобиться заменить тестовыми? И вообще зачем бизнес логике что то знать о источнике данных? Для решениях этих проблем существует слой Repository.
Паттерн Repository
Repository — отвечает за общение с хранилищем данных. В качестве хранилища может выступать сервер, база данных, память, localstorage, sessionstorage или любое другое хранилище. Его задача абстрагировать слой Service от конкретной реализации хранилища.
Давайте вынесем сетевые запросы из сервисов в репозитории, контроллер при этом не меняем:
Теперь достаточно один раз написать запрос к данным и любой сервис сможет переиспользовать этот запрос. Позже мы рассмотрим пример как переопределить репозиторий не трогая код сервиса и внедрить моковый репозиторий на время тестирования.
В сервисе UserProfilService может показаться что он не нужен и контроллер может напрямую обратиться к репозиторию за данными, но это не так. В любой момент в бизнес слое могут появиться или измениться требования, может потребоваться дополнительный запрос или обогатить данные. Поэтому даже когда в слое сервиса нету логики цепочка Controller — Service — Repository должна сохраняться. Это вклад в ваше завтра.
Настало время разобраться что за заданные получает репозиторий, корректные ли они вообще. За это отвечает слой Models.
Модели: DTO, Entities, ViewModels
Models — отвечает за описание структур с которыми работает приложение. Такое описание очень помогает новым разработчикам проекта понять с чем работает приложение. Кроме того его очень удобно использовать для построения баз данных или проведений валидаций данных хранящихся в модели.
Добавим в приложение модель профиля пользователя и другие модели, и сообщим остальным слоям что теперь мы работаем не с абстрактным объектом, а с вполне конкретным профилем:
Архитектурный дизайн мобильных приложений
Признак плохого дизайна №1:
Наличие объекта-«бога» с именем, содержащим «Manager», «Processor» или «API»
Ведущий iOS-разработчик Redmadrobot Егор BepTep Тафланиди — о том, как добиться стройного архитектурного дизайна мобильного приложения, используя классические шаблоны проектирования и логическое разделение исходного кода на модули.
Все архитекторы, которых я встречал, в разной степени обладали одной и той же профессиональной деформацией характера: в своей речи они старались избегать конкретики. Подобный подход легко понять, ведь суть гибкости любой системы заключается в абстрагировании от конкретных решений. Чем дольше решение может оставаться отсроченным — тем гибче система. Если UI достаточно абстрагирован от модулей уровня сохранения данных — то считывание файла с жёсткого диска с лёгкостью можно будет подменить скачиванием той же информации с серверного API.
И все же, попробуем разложить по полочкам архитектурный дизайн типичного iOS-приложения.
Я опишу глобальный подход к делению логики приложения на уровни и слои, а также приведу конкретных участников тех или иных процессов, каждый — со своей собственной ответственностью и взаимодействиями с окружением.
Установка
TL;DR: ПРОГРАММИСТЫ — ТОЖЕ НАУЧНЫЕ СОТРУДНИКИ
Студенты высших учебных заведений изучают естественные науки, учатся менеджменту, получают знания о прикладной психологии и пр. Каждая наука несёт в себе обязательный багаж накопленных сведений — академические знания, без которых продвижение самой науки вперёд было бы затруднительно. Без этого фундамента от науки не было бы и практической пользы: технологу на производстве гораздо выгоднее использовать проверенные временем подходы, гарантированно дающие результат, чем придумывать что-то своё. Естественно, сам этот фундамент постоянно эволюционирует и достраивается новыми сведениями, практиками.
За спиной программирования стоит точно такая же строгая наука, как и, например, за процессом производства аспирина. Информатика, как типичный представитель формальных наук, обладает тем же набором и структурой формальных методов, использует наработанные, проверенные подходы к изучению нового, и, естественно, к ней прилагается собственный багаж академических знаний. Научный подход гарантированно ведёт к получению качественного результата, а знание и применение базовых принципов проектирования ПО гарантированно облегчит поддержку продукта.
Книга «Язык паттернов» Кристофера Александра, давшая толчок в сторону использования шаблонов в программировании, была выпущена в 1977 году, а паттерн Model-View-Controller описан в 1979-м.
Вооружившись наработками в области разработки ПО и надев белый халат теоретика, попробуем выстроить типичный проект приложения для мобильной платформы.
N.B. Скорее всего, в реальной жизни (с учётом бизнес-требований к системе и принимая во внимание сроки разработки) ваш проект будет выглядеть несколько иначе. Тем не менее, можно будет с уверенностью сказать, что за спиной подобного продукта стоит не просто ваша выдумка, а обоснованный набор решений.
СФЕРИЧЕСКАЯ СИСТЕМА В ВАКУУМЕ
Модель песочных часов
СИТХОВ ВСЕГДА ДВОЕ: ОДИН СТРОИТ СЕРВИСЫ, ДРУГОЙ ВОЗДВИГАЕТ НА НИХ UI
Первым же шагом отделим модель от контроллера и представления, таким образом выделив два уровня приложения: «нижний» обращён в сторону серверных мощностей, «верхний» смотрит на пользователя.
На проектах мобильных приложений слои контроллера и представления, как правило, довольно органично способны разрабатываться одним человеком, в то время как остальная бизнес-логика, касающаяся персистентности и обработки данных, безболезненно может быть доверена второму разработчику. Тем самым мы сразу распараллеливаем процесс изготовления ПО на два независимых потока.
Будем придерживаться классического принципа, который гласит, что слои приложения общаются между собой посредством модельных объектов — классов с «прозрачной» реализацией, состоящей исключительно из акцессоров и мутаторов (get- и set-методы; фактически, класс будет включать в себя только свойства). Подобные классы не несут с собой никакой логики, и предназначены лишь для хранения и перемещения данных. 
Центральной сущностью приложения — «горлышком» песочных часов — будет выступать объект, иначе называемый «инвертор зависимостей». Он же станет единственным singleton-классом, и на нём будет лежать единственная ответственность — предоставлять «верхним» слоям приложения сервисы.
Сервисы
ИСТОРИЯ ШТЕПСЕЛЬНОГО СОЕДИНЕНИЯ
Как делаются нормальные серверные приложения? Очень просто. Всегда есть какая-то база данных (или их может быть несколько), в которой есть таблицы. Таблицы состоят из записей. Каждая запись содержит набор полей, в которых хранится информация.
Записи представляются в виде модельных объектов с полями, тем самым в приложении возникает логическое разграничение по типам данных: одна таблица — один тип данных. Таблица «Пользователи» — тип данных «Пользователь» (поля: ID, ФИО, адрес). Таблица «Сообщения» — тип данных «Сообщение» (поля: ID, Тема сообщения, Тело сообщения, Кому, От кого, Дата). И так далее.
Таким образом, вся логика крутится вокруг таблиц и типов данных:
1. Модельный объект, представляющий тип данных.
2. Сериализация модельного объекта для сохранения его в таблицу.
3. Десериализация модельного объекта из базы данных.
4. Преобразования модельного объекта: подсчёт какой-то статистики,
сортировка данных, поиск данных и т.д.
5. Сериализация модельного объекта для передачи его по сети.
6. Десериализация модельных объектов, приходящих по сети.
7. Выставленный в сеть web-сервис, соответствующий данному типу
данных.
Всё это выстраивается в своего рода стек, а само серверное приложение состоит из нескольких таких независимых стеков, каждый из которых соответствует какому-то типу данных.
Грубо говоря, если по спецификации API у вас имеется web-сервис api.domain.com/1-0/messages с классическим CRUD-интерфейсом — это означает, что за ним стоит вышеупомянутый «стек» для типа данных «сообщение».
Create = POST;
Read = GET;
Update = PUT;
Delete = DELETE.
N.B. Существуют различные интерпретации операторов POST и PUT. В одних реализациях POST отвечает за создание сущностей, в других — за обновление. Канонического трактования, увы, не существует, но в этом нет ничего страшного.
Как правило, сервер поддерживает стандартизованный набор запросов, дифференцируемый посредством URL-суффиксов к соответствующему web- сервису:
ИТОГО:
Всё, что нам следует сделать — это изготовить вилку к розетке.
А именно:
1. Оформить транспортный уровень приложения в виде сущности, которая будет инкапсулировать под собой HTTP-соединение вместе со всеми его настройками (включая безопасность, таймауты и пр.)
• Интерфейс сущности должен представлять собой точно такой же CRUD, как и выставленный в Интернет web-сервис. Если есть доступ к адресу
• Сущность обязана быть модульной. Если в один прекрасный момент ваш замечательный web-сервис
файловой системы.
2. Оформить парсеры и сериализаторы, которые будут генерировать модельные объекты и, наоборот, преобразовывать их в вид, удобоваримый для вашего транспортного уровня.
•Парсеры и сериализаторы несут в себе знания о том, как преобразовывать модельные объекты в словари а-ля JSON или XML. Не пытайтесь прикрутить эти знания к самим модельным объектам — они никак не относятся к другим слоям приложения.
• Существует русскоязычный термин «топографический преобразователь», который мог бы заменить англоязычные «парсер» и «сериализатор», но только вот сам термин как-то не прижился…
3. Оформить сущность, которая будет отвечать за кеширование.
• Сущность должна просто уметь потокобезопасно предоставлять доступ к полученным данным, переписывать и обновлять эти данные более свежими.
4. Оформить сам «сервис» — сущность, которая будет ответственна за координацию транспортного уровня, парсеров, сериализаторов и кеша.
• Интерфейс сервиса должен полностью соответствовать бизнес-требованиям слоя UI. Если для UI необходимо получить какую-то сущность — должен быть метод для получения этой сущности. Если UI требует сохранить сущность с определёнными параметрами — у сервиса должен быть метод с этими параметрами. В случае, если UI требует массив объектов данного типа, отсортированный по какому-то критерию, — сервис должен предоставлять соответствующий метод, который вернёт отсортированный массив.
• И да, не забываем классический принцип: слои приложения общаются между собой посредством модельных объектов. Использование словарей/map для передачи данных недопустимо — только строгая типизация.
Таким образом, мы фактически делаем свой собственный стек, соответствующий серверному, но действующий в обратную сторону. Вот так будет выглядеть последовательность из шагов, которые необходимо преодолеть данным, чтобы добраться от центрального хранилища до конечного пользователя: 
Сколько типов данных — столько и сервисов. Эту цифру довольно просто вычислить из спецификации API.
N.B. «Чистые» сервисы в природе встречаются не так часто. Бывает, тот или иной сервис обслуживает сразу несколько типов данных. Это обусловлено тем, что модельные сущности могут быть вложенными друг в друга: одна сущность может нести в себе массив объектов другого типа, или иметь подобный объект в качестве свойства.
В этом нет ничего страшного, просто ваши парсеры и сериализаторы должны адекватно воспринимать подобного рода вложенность, и генерировать объекты соответствующих типов.
Сервисы обязаны быть максимально автономными, что не мешает вам выстраивать между ними логические зависимости. К примеру, это вполне логично, что в приложении есть сервис, отвечающий за авторизацию и поддержание сессии с сервером, а остальные сервисы зависят от него — используют предоставляемый им token для формирования запросов.
N.B. Каждый из сервисов так или иначе будет обладать CRUD-подобным интерфейсом, построенным вокруг типа данных, обрабатываемых этим сервисом. Это может служить отличным поводом для выделения абстрактного сервиса с абстрактным CRUD-интерфейсом для последующего наследования от него других сервисов, что обеспечит высокую степень переиспользования кода.
Выстраивание уровня model подобным образом суть реализация паттерна слоистой архитектуры с наложением идей сервис-ориентированной архитектуры.
Мы не изобрели ничего нового.
Уровень UI
ВРЕМЯ ЗАМЕЧАТЕЛЬНЫХ ИСТОРИЙ
Итак, вы пришли на проект, скачали с репозитория исходники разрабатываемого приложения. Какой самый простой способ выяснить, что это приложение делает? Правильно! Запустить его! Перед вами знакомые таблицы с ячейками, стилизованные кнопки, барабаны с датами, навигация, названия экранов… Теперь задача: как соотнести это всё с исходным кодом, который разбросан по нескольким сотням файлов, что маячат перед вами в IDE?
Мы уже обособили один из уровней приложения — фактически model теперь может выступать совершенно самостоятельным модулем, который можно переподключить к какому-нибудь другому проекту, использующему тот же back-end. Или даже переиспользовать для другого back-end’a, если вы соблюли все принципы проектирования и сохранили определённый уровень абстракции.
Но вот незадача: у нас ещё целых два (!) слоя, а единственная намётка на то, что и где в исходных кодах находится — это само работающее приложение, запущенное на устройстве или в эмуляторе. Тут-то на помощь и приходят пользовательские истории.
Если ваше приложение хоть сколько-нибудь соответствует здравому смыслу, оно будет следовать определённым пользовательским историям, которые декларируют сценарии, необходимые для реализации того или иного действия. Самый классический пример — это истории регистрации и авторизации. Во время регистрации пользователь вводит какие-то личные данные, эти данные проверяются на адекватность, задаются логины и пароли, всё это может быть сдобрено мерами безопасности в виде SMS и так далее. Соответственно, в приложении существует целая история, касающаяся регистрации. Следуя логике, дабы облегчить собственную жизнь, наиболее очевидным решением будет следование этим пользовательским историям при построении структуры уровня UI.
Более того, SDK приложений под iOS предоставляют весь необходимый для этого инструментарий: достаточно просто завести под каждую историю свой Storyboard — и разделить структуру проекта так, чтобы все классы строились вокруг этих историй. Далее эти Storyboard’ы можно даже динамически соединять друг с другом посредством нехитрых библиотек, но это уже детали реализации.
В конечном итоге у вас получится структура, разделенная на модули по историям, в каждом модуле будут свои представления и контроллеры с классами-утилитами, и в этой структуре можно будет легко ориентироваться, исходя лишь из того, что у вас творится на экране устройства/эмулятора.
N.B. И да, конечно, не забываем классические принципы проектирования. Применяем SOLID, DRY, KISS и YAGNI по полной программе, главное — не лениться, и разделять сущности в соответствии с ответственностью, которую они несут. Остальное подскажет опыт.
Можно набросать приблизительную диаграмму классов: 
Естественно, совершенству нет предела, и данный подход не претендует на звание эталонного.
Заключение
ИТОГИ И ОБСУЖДЕНИЕ
Мы кратко рассмотрели, как можно возвести стройный архитектурный дизайн мобильного приложения. Для этого мы применили сплав из классических шаблонов проектирования (MVC, SOA и Multilayered Architecture), прибегнув также к логическому разделению исходного кода на модули, основываясь на пользовательских историях.
А теперь небольшой вопрос к community:
Использовал ли кто-нибудь на боевых проектах архитектуру View-Interactor-Presenter-Entity-Router, и каким образом было достигнуто переиспользование сущности/шаблона Router? Как можно избежать разрастания Router’a?
Архитектура приложения малой кровью
Маленькая зарисовка на тему того, как разработать высокоуровневую архитектуру приложения.
Предположим, что с будущей функциональностью Вы определились. Тогда Вы точно знаете, кто или что будет поставлять данные, кто/что будет их потреблять и какие преобразования данных должна выполнять сама система.
Возьмите лист бумаги и карандаш. Если не сильно уверены в своих силах, то ещё и резинку, чтобы править схему. Более продвинутые читатели могут обратиться к профессиональным инструментам для проектирования архитектуры в электронном виде.
Теперь выясните, кто будет обращаться к вашей системе, чтобы передать или забрать данные, а к чему будет обращаться Ваша программа. Те системы или пользователи, которые обращаются к программе сами, нарисуйте схематически на листе бумаги вверху. Те, к которым будет обращаться программа (включая БД), — снизу.
Теперь нарисуйте под каждым нарисованным сверху субъектом прямоугольник с названием UI или API — это интерфейсы, к которым будет обращаться пользователь или внешняя управляющая система. Иногда UI тоже может обращаться к API. Объедините все прямоугольники с UI одним контуром и обзовите слоем представления. Объедините все прямоугольники с API и обзовите слоем сервисов.
Для систем, нарисованных снизу, укажите компоненты, которые будут отвечать за доступ к этим системам. Объедините все эти компоненты одним контуром и обзовите слоем доступа к данным.
Между слоем сервисов и слоем доступа к данным нарисуйте большой контур и назовите его слоем бизнес-логики. В маленьких прямоугольниках внутри этого контура перечислите основные бизнес-задачи. Один компонент Вашей системы будет решать одну бизнес-задачу.
Теперь справа нарисуйте несколько длинных прямоугольников снизу доверху и напишите в них: журналирование, конфигурация, мониторинг производительности, обработка исключений и что-то ещё, что является общей инфраструктурой (или сквозной функциональностью) для всех слоёв вашей программы.
Вы получили логическую архитектуру приложения. Разбросайте слои по серверам — получите физическую архитектуру.
Теперь вам остаётся детально проработать каждый маленький квадратик.
Маленький практический пример запрячу под кат.
Для примера я возьму совершенно виртуальный проект системы контроля успеваемости для средней школы. Возьму её по двум причинам. Во-первых, все мы учились в школе. Во-вторых, у многих из нас дети сейчас учатся в той же школе. Из-за этого, надеюсь, предметная область будет всем понятна.
Итак, наша система будет предназначена для ведения электронных дневников учащихся и электронных классных журналов. В довесок пусть система имеет некоторый дополнительный набор функций, позволяющий учащимся, родителям и преподавателям обмениваться сообщениями, а руководству школы — контролировать учебный процесс.
Итак, пользователями нашей системы будут учащиеся, их родители, учителя и руководство школы. Кроме того, пользователями будут являться администраторы системы, которым важно получать информацию о событиях в системе и выполнять некоторые сервисные операции. Перечислять варианты использования не будем, не о них речь.
Пользователи будут взаимодействовать с нашей системой либо посредством web-интерфейса, либо с помощью мобильного приложения. Оба пользовательских интерфейса предназначены для школ, которые не могут себе позволить разработку своего собственного сайта. Остальные смогут обращаться к нашей системе через web-сервисы.
Сервисов будет четыре: электронный дневник, электронный журнал, организация учебного процесса и администрирование.
Под слоем сервисов будет слой бизнес-логики, состоящий из десятка компонентов, решающих бизнес-задачи пользователей.
Система будет экспортировать/импортировать данные из файлов. Кроме того, будет две внешние системы, к которым наша система будет подключаться удалённо: система справочной информации и информационная система Департамента образования.
Сквозную функциональность будут составлять механизмы журналирования, мониторинга, управления конфигурацией, обеспечения безопасности.
В итоге, получаем вот такую простую схему:
Теперь можно выделить физические узлы. Web-интерфейс пользователя и сервисы будут развёрнуты в web-кластере. Слои бизнес-логики и доступа к данным будут реализованы на сервере приложений. Базы данных разместятся в отдельном отказоустойчивом кластере. Позже можно будет все узлы изобразить в виде красивой схемы физической архитектуры.
Надеюсь, пример понятен.
Для того, чтобы более глубоко погрузиться в тему, предлагаю обратить внимание на «Руководство Microsoft по проектированию архитектуры приложений». Руководство написано в форме справочника, так что можно быстро найти рекомендации по проектированию каждого слоя системы.





