# Вспомогательный материал №2 ## О том, как работает интернет Интернет является основой сети (the Web), технической инфраструктурой, благодаря которой и существует Всемирная Паутина. По своей сути, интернет - очень большая сеть компьютеров, которые могут взаимодействовать друг с другом. [История интернета не до конца ясна](https://www.wikiwand.com/ru/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82#/.D0.98.D1.81.D1.82.D0.BE.D1.80.D0.B8.D1.8F). Проект по созданию интернета был начат в 60-х годах как исследовательский проект при поддержке министерства обороны США, но уже в 80-е годы вырос в сеть, которую поддерживали и развивали множество университетов и частных компаний. Технологии, лежащие в основе интернета, также продолжали развиваться со временем, но основной принцип работы не сильно изменился: Интернет - это способ подключить компьютеры в единую сеть и убедиться, что даже при серьёзных сбоях, они всё равно найдут способ связаться друг с другом. ### Простая сеть Когда нужно связать между собой два компьютера, вы должны связать их в сеть либо проводным (обычно с помощью Ethernet кабеля), либо беспроводным способом (например, с помощью WiFi или Bluetooth). Современные компьютеры поддерживают любой из этих способов связи. Примечание: мы будем говорить только о физическом (проводном) способе подключения, но беспроводные сети работают аналогичным образом. Таким способом вы можете подключить более двух компьютеров, но с каждым новым это становится все сложнее. Если хочется подключить, скажем, 10 компьютеров, вам понадобится 45 кабелей и 9 сетевых плат в каждом компьютере! Чтобы решить эту проблему, каждый компьютер в сети подключается к специальному маленькому компьютеру. Этот компьютер называют маршрутизатором. Маршрутизатор исполняет только одну роль: как сигнальщик на железной дороге он следит за тем, чтобы пакет, отправленный одним компьютером — источником — достиг пункта назначения. Чтобы отправить сообщение компьютеру B, компьютер A сначала должен отправить его маршрутизатору, который перенаправит его компьютеру B и проконтролирует, чтобы данные не попали компьютеру C. С добавлением маршрутизатора наша сеть здорово упрощается: чтобы соединить 10 компьютеров нам требуется только 10 кабелей (каждый кабель соединяет маршрутизатор с одним из компьютеров). Пока все нормально. Но что нам делать, если нужно объединить в сеть сотни, тысячи или миллиарды компьютеров? Конечно, один маршрутизатор не справится с этой задачей, но если вы внимательно читали, то помните, что маршрутизатор — это обычный компьютер, и ничто не мешает нам соединить друг с другом 2 маршрутизатора. Подключая компьютеры к маршрутизатору, а затем — маршрутизатор к другому маршрутизатору, мы можем увеличивать нашу сеть до сколь угодно больших размеров. Такая сеть уже очень похожа на то, что мы называем интернетом, но мы что-то упустили. Наша сеть построена для решения только наших задач. Но кроме неё есть и другие сети: наши друзья, соседи — кто угодно может создать свою сеть. Как же нам их объединить? Мы не можем протянуть кабели между нашим домом и всеми остальными сетями в мире. Чтобы решить эту проблему, мы можем воспользоваться уже существующими кабельными сетями. Ведь у нас дома уже есть кабели, например, электрические или телефонные. Телефонный провод уже соединяет ваш дом со всем остальным миром, так что он идеально подходит для решения нашей задачи. Чтобы подключить нашу сеть к глобальной сети с помощью телефонного провода, нам понадобится специальное оборудование, которое называется модем. Модем перекодирует информацию, поступающую из нашей сети в формат, который можно передавать через телефонную сеть, и наоборот, декодируют информацию из телефонной сети в формат, который распознают наши компьютеры. Итак, мы подключились к телефонной сети. Следующий шаг — передать сообщение из нашей сети в сеть, с которой мы хотим связаться. Чтобы сделать это, мы должны подключить нашу сеть к провайдеру услуг интернета (Internet Service Provider (ISP)). Провайдер — компания, которая обслуживает специальные маршрутизаторы, которые не только подключены друг к другу (объединяют в единую сеть всех клиентов провайдера), но также связаны с маршрутизаторами других провайдеров. Таким образом, наше сообщение, пройдя транзитом через сеть нескольких провайдеров, достигнет сеть назначения. Интернет — это сеть сетей, которая объединяет в себе всю вышеперечисленную инфраструктуру. Чтобы послать сообщение какому-то компьютеру, необходимо как-то обратиться к нему, выделить среди других. Поэтому каждый компьютер, подключённый к сети, имеет свой уникальный адрес для связи: этот адрес называют IP-адресом (IP — сокращение для Internet Protocol, протокол интернета). В зависимости от версии протокола IP этот адрес может записываться по-разному. Самая широко используемая версия интернет-протокола — версия 4. Адреса IPv4 обычно записываются в виде четырёх чисел, разделённых точками, например: 192.168.2.10. Такие адреса отлично подходят для компьютеров, но людям очень сложно их запоминать. Чтобы упростить себе жизнь, мы можем присвоить каждому IP-адресу псевдоним с понятным для человека именем. Такой псевдоним называют доменным именем. Например, google.com — доменное имя, которое является псевдонимом IP-адреса 173.194.121.32. Использование доменного имени — самый простой способ обратиться к компьютеру в интернете. Как вы уже заметили, когда мы просматриваем Веб с помощью браузера, обычно мы используем доменное имя, чтобы обратиться к веб-сайту. Означает ли это, что Интернет и Веб — это одно и то же? Ответ не так прост. Мы уже знаем, что Интернет — это техническая основа, которая позволяет миллиардам компьютеров связываться друг с другом. Среди этих компьютеров есть небольшая группа (называемая веб-серверами), которые могут отправлять сообщения, распознаваемые браузерами. Интернет — это инфраструктура, а Веб — это сервис, построенный на основе этой инфраструктуры. Стоит отметить, что кроме Веба есть и другие сервисы, построенные на базе Интернета. ## Веб Как работает Веб даст упрощённое представление о том, что происходит при просмотре веб-страницы в браузере на вашем компьютере или телефоне. Эта теория не так важна для написания веб-кода в краткосрочной перспективе, но в скором времени вы действительно начнёте извлекать выгоду из понимания того, что происходит в фоновом режиме. Компьютеры, подключённые к сети называются клиентами и серверами. Упрощённая схема того, как они взаимодействуют, может выглядеть следующим образом: ![](https://i.imgur.com/Nj1Z3bI.png) * Клиенты являются обычными пользователями, подключёнными к Интернету посредством устройств (например, компьютер подключён к Wi-Fi, или ваш телефон подключён к мобильной сети) и программного обеспечения, доступного на этих устройствах (как правило, браузер, например, Firefox или Chrome). * Серверы - это компьютеры, которые хранят веб-страницы, сайты или приложения. Когда клиентское устройство пытается получить доступ к веб-странице, копия страницы загружается с сервера на клиентский компьютер для отображения в браузере пользователя. Клиент и сервер не раскрывают всю суть. Есть много других компонентов. А сейчас давайте представим, что Веб - это дорога. Одна сторона дороги является клиентом, который представляет собой ваш дом. Другая сторона дороги является сервером, который представляет собой магазин. Вы хотите что-то купить в нём. Помимо клиента и сервера, мы также должны уделить внимание: * Ваше Интернет-подключение: Позволяет отправлять и принимать данные по сети. Оно подобно улице между домом и магазином. * TCP/IP: Протокол Управления Передачей и Интернет Протокол являются коммуникационными протоколами, которые определяют, каким образом данные должны передаваться по сети. Они как транспортные средства, которые позволяют сделать заказ, пойти в магазин и купить ваши товары. В нашем примере, это как автомобиль или велосипед (или собственные ноги). * DNS: Система Доменных Имён напоминает записную книжку для веб-сайтов. Когда вы вводите веб-адрес в своём браузере, браузер обращается к DNS, чтобы найти реальный адрес веб-сайта, прежде чем он сможет его получить. Браузеру необходимо выяснить, на каком сервере живёт сайт, поэтому он может отправлять HTTP-сообщения в нужное место (см. Ниже). Это похоже на поиск адреса магазина, чтобы вы могли попасть в него. * HTTP: Протокол Передачи Гипертекста - это протокол, который определяет язык для клиентов и серверов, чтобы общаться друг с другом. Он, как язык, который вы используете, чтобы заказать ваш товар. * Файлы компонентов: сайт состоит из нескольких различных файлов, которые подобны различным отделам с товарами в магазине. Эти файлы бывают двух основных типов: * Файлы кода: сайты построены преимущественно на HTML, CSS и JavaScript, хотя вы познакомитесь с другими технологиями чуть позже. * Материалы: это собирательное название для всех других вещей, составляющих сайт, такие как изображения, музыка, видео, документы Word и PDF. Когда вы вводите веб-адрес в свой браузер (для нашей аналогии - посещаете магазин): * Браузер обращается к DNS серверу и находит реальный адрес сервера, на котором "живёт" сайт (Вы находите адрес магазина). * Браузер посылает HTTP запрос к серверу, запрашивая его отправить копию сайта для клиента (Вы идёте в магазин и заказываете товар). Это сообщение и все остальные данные, передаваемые между клиентом и сервером, передаются по интернет-соединению с использованием протокола TCP/IP. * Если сервер одобряет запрос клиента, сервер отправляет клиенту статус "200 ОК", который означает: "Конечно, вы можете посмотреть на этот сайт! Вот он", а затем начинает отправку файлов сайта в браузер в виде небольших порций, называемых пакетными данными (магазин выдаёт вам ваш товар или вам привозят его домой). * Браузер собирает маленькие куски в полноценный сайт и показывает его вам (товар прибывает к вашей двери — новые вещи, потрясающе!). Реальные веб-адреса - неудобные, незапоминающиеся строки, которые вы вводите в адресную строку, чтобы найти ваши любимые веб-сайты. Эти строки состоят из чисел, например: 63.245.215.20. Такой набор чисел называется IP-адресом и представляет собой уникальное местоположение в Интернете. Впрочем, его не очень легко запомнить, правда? Вот почему изобрели DNS. Это специальные сервера, которые связывают веб-адрес, который вы вводите в браузере (например, "mozilla.org"), с реальным IP-адресом сайта. Ранее был использован термин "пакеты", чтобы описать формат, в котором данные передаются от сервера к клиенту. Что мы имеем в виду? В основном, когда данные передаются через Интернет, они отправляются в виде тысячи мелких кусочков, так что множество разных пользователей могут скачивать один и тот же сайт одновременно. Если бы сайты отправлялись одним большим куском, тогда бы только один пользователь мог скачать его за один раз, и это, очевидно, сделало бы пользование интернетом не эффективным и не очень радостным. ## Основы TCP/IP Стек протоколов TCP/IP (Transmission Control Protocol/Internet Protocol, протокол управления передачей/протокол интернета) — сетевая модель, описывающая процесс передачи цифровых данных. Она названа по двум главным протоколам, по этой модели построена глобальная сеть — интернет. Сейчас это кажется невероятным, но в 1970-х информация не могла быть передана из одной сети в другую, с целью обеспечить такую возможность был разработан стек интернет-протоколов также известный как TCP/IP. Разработкой этих протоколов занималось Министерство обороны США, поэтому иногда модель TCP/IP называют DoD (Department of Defence) модель. Если вы знакомы с моделью OSI, то вам будет проще понять построение модели TCP/IP, потому что обе модели имеют деление на уровни, внутри которых действуют определенные протоколы и выполняются собственные функции. Мы разделили статью на смысловые части, чтобы было проще понять, как устроена модель TCP/IP: ![](https://i.imgur.com/mwghCFA.png) Принципы работы интернет-протоколов TCP/IP по своей сути очень просты и сильно напоминают работу нашей почты. Вспомните, как работает наша обычная почта. Сначала вы на листке пишете письмо, затем кладете его в конверт, заклеиваете, на обратной стороне конверта пишете адреса отправителя и получателя, а потом относите в ближайшее почтовое отделение. Далее письмо проходит через цепочку почтовых отделений до ближайшего почтового отделения получателя, откуда оно тетей-почтальоном доставляется до по указанному адресу получателя и опускается в его почтовый ящик (с номером его квартиры) или вручается лично. Все, письмо дошло до получателя. Когда получатель письма захочет вам ответить, то он в своем ответном письме поменяет местами адреса получателя и отправителя, и письмо отправиться к вам по той же цепочке, но в обратном направлении. В компьютерных сетях, работающих по протоколам TCP/IP, аналогом бумажного письма в конверте является пакет, который содержит собственно передаваемые данные и адресную информацию — адрес отправителя и адрес получателя Каждый компьютер (он же: узел, хост) в рамках сети Интернет тоже имеет уникальный адрес, который называется IP-адрес (Internet Protocol Address). Но знать только IP адрес компьютера еще недостаточно, т.к. в конечном счете обмениваются информацией не компьютеры сами по себе, а приложения, работающие на них. А на компьютере может одновременно работать сразу несколько приложений (например почтовый сервер, веб-сервер и пр.). Для доставки обычного бумажного письма недостаточно знать только адрес дома — необходимо еще знать номер квартиры. Также и каждое программное приложение имеет подобный номер, именуемый номером порта. Большинство серверных приложений имеют стандартные номера, например: почтовый сервис привязан к порту с номером 25 (еще говорят: «слушает» порт, принимает на него сообщения), веб-сервис привязан к порту 80, FTP - к порту 21 и так далее. Конечно же в пакетах также присутствует служебная информация, но для понимания сути это не важно. Обратите внимание, комбинация: "IP адрес и номер порта" - называется "сокет". Что такое маска подсети и шлюз по умолчанию (роутер, маршрутизатор)? (Эти параметры задаются в настройках сетевых подключений). Все просто. Компьютеры объединяются в локальные сети. В локальной сети компьютеры напрямую «видят» только друг друга. Локальные сети соединяются друг с другом через шлюзы (роутеры, маршрутизаторы). Маска подсети предназначена для определения — принадлежит ли компьютер-получатель к этой же локальной сети или нет. Если компьютер-получатель принадлежит этой же сети, что и компьютер-отправитель, то пакет передается ему напрямую, в противном случае пакет отправляется на шлюз по умолчанию, который далее, по известным ему маршрутам, передает пакет в другую сеть, т.е. в другое почтовое отделение (по аналогии с почтой). Напоследок рассмотрим что же означают непонятные термины: TCP/IP — это название набора сетевых протоколов. На самом деле передаваемый пакет проходит несколько уровней. (Как на почте: сначала вы пишете писмо, потом помещаете в конверт с адресом, затем на почте на нем ставится штамп и т.д.). IP протокол — это протокол так называемого сетевого уровня. Задача этого уровня — доставка ip-пакетов от компьютера отправителя к компьютеру получателю. Помимо собственно данных, пакеты этого уровня имеют ip-адрес отправителя и ip-адрес получателя. Номера портов на сетевом уровне не используются. Какому порту, т.е. приложению адресован этот пакет, был ли этот пакет доставлен или был потерян, на этом уровне неизвестно — это не его задача, это задача транспортного уровня. TCP и UDP — это протоколы так называемого транспортного уровня. Транспортный уровень находится над сетевым. На этом уровне к пакету добавляется порт отправителя и порт получателя. TCP — это протокол с установлением соединения и с гарантированной доставкой пакетов. Сначала производится обмен специальными пакетами для установления соединения, происходит что-то вроде рукопожатия (-Привет. -Привет. -Поболтаем? -Давай.). Далее по этому соединению туда и обратно посылаются пакеты (идет беседа), причем с проверкой, дошел ли пакет до получателя. Если пакет не дошел, то он посылается повторно («повтори, не расслышал»). UDP — это протокол без установления соединения и с негарантированной доставкой пакетов. (Типа: крикнул что-нибудь, а услышат тебя или нет — неважно). Над транспортным уровнем находится прикладной уровень. На этом уровне работают такие протоколы, как http, ftp и nр. Например HTTP и FTP — используют надежный протокол TCP, а DNS-сервер работает через ненадежный протокол UDP. Текущие соединения можно посмотреть с помощью команды ``` netstat -an ``` (параметр n указывает выводить IP адреса вместо доменных имен). Запускается эта команда следующим образом: «Пуск» - «Выполнить» - набираем cmd - «Ок». В появившейся консоли (черное окно) набираем команду netstat -an и жмем `<Enter>`. Результатом будет список установленных соединений между сокетами нашего компьютера и удаленных узлов. Например получаем: ![](https://i.imgur.com/FbfJ03I.png) В этом примере 0.0.0.0:135 — означает, что наш компьютер на всех своих IP адресах слушает (LISTENING) 135-й порт и готов принимать на него соединения от кого угодно (0.0.0.0:0) по протоколу TCP. 91.76.65.216:139 — наш компьютер слушает 139-й порт на своем IP-адресе 91.76.65.216. Третья строка означает, что сейчас установлено (ESTABLISHED) соединение между нашей машиной (91.76.65.216:1719) и удаленной (212.58.226.20:80). Порт 80 означает, что наша машина обратилась с запросом к веб-серверу (у меня, действительно, открыты страницы в браузере). ## Бизнес Логика Для чего нужна программа? Большинство новичков-программистов говорят: я буду писать программы. Программа – это что-то кому-то нужное. Ее кто-то будет использовать, чтобы достигать свои цели. Если это бизнес-программа – в ней будут обрабатываться бизнес-данные. Это может быть бухгалтерская программа, складская, программа управления персоналом, логистикой, телекомом, – неважно, но она должна делать что-то полезное для бизнеса. Например, учитывать товары, которые приходят на склад На склад приходит товар. Кладовщик-приемщик должен взять и вбить в программу товары, которые пришли, и сколько. Потому что склад большой. И когда к нему придут и спросят – а есть у тебя пять ведер? Он должен открыть программу и посмотреть – да, пять ведер есть. И если спросят, то проверить, по какой накладной и от какого поставщика они пришли. Вроде бы просто. Но если бы не было программы, ему бы пришлось все это записывать на бумажках. И если склад большой, то разбираться в этих бумажках можно год. Или два. И не разобраться. Поэтому программа нужна, и такие программы пишутся в интересах пользователей. Если мы говорим про бизнес, для которого и пишутся 95% программ, то логика программы – то, что она делает – это и есть бизнес-логика. По-английски – domain. Это правила, которые содержатся в самом бизнесе. Например, не может существовать накладная без единой позиции – это значит, что ничего не пришло, и накладной просто нет. Или нельзя отправить со склада товар, которого на складе нет и не было. Самые простые требования, которые вам придут в голову первыми. Но в любом бизнесе таких правил – тысячи. Ваша программа фактически будет содержать эти правила, и следить за тем, чтобы правила соблюдались пользователями. В случае со складом это кладовщик. У любого приложения есть три слоя. Слой пользовательского интерфейса – это вывод данных, кнопочки, списки, стрелочки, и другие элементы, которые показываются пользователю. Слой бизнес-логики – тот самый, где прописаны бизнес-правила по требованию заказчика. И слой сохранения данных, он же consistency. Мы его пока не трогаем. Надо понимать, что существует огромное количество однотипных задач. Например, вывод данных в интерфейс. Для этого существуют фреймворки – набор принятых решений. Это способ автоматизации, когда программист просто подключает фреймворк, чтобы сразу нарисовался красивый интерфейс. Интерфейсы плюс-минус однотипные во всех приложениях: кнопочки, списочки, все относительно стандартно. А вот слой бизнес-логики, где заданы правила, не может быть создан таким способом. В каждом бизнесе, даже если это пять складов, в каждом будут свои особенности. Допустим, на первом складе два помещения, и кладовщику надо знать, где именно хранится товар. А у другого склада отделения в трех городах, и надо знать, в каком городе. А у третьего склада одна каптерка, помещения ему не нужны. Зато от может отгружать товары с соседнего склада. Это простой пример, а таких правил может быть миллионы. Например, если мы говорим о банковской сфере или телекоме, там очень сложная логика. Посмотрите как-нибудь свой договор с банком, удивитесь. Десять страниц мелким текстом. Это и есть бизнес-логика – как банк работает с вашим счетом. Персональные счета, накопительные, и так далее. Спросите любого разработчика, где должна быть бизнес логика, и получите ответ: «Конечно же в бизнес слое». Спросите того же разработчика, где находится бизнес логика в их организации, и снова услышите: «Конечно же в бизнес слое». У вас не должно быть не малейших сомнений на счет того где должна быть бизнес логика – в бизнес слое. Не часть бизнес логики – вся бизнес логика должна быть в бизнес слое. Звено - подразумевается физическое звено состоящее из физического сервера или группы серверов, выполняющих одинаковую функцию и сгруппированных только для повышения емкости. Слой - подразумевается сегмент системы, который ограничен собственным процессом или модулем. Множество слоев может содержаться в одном звене, но любой из них должен иметь возможность быть легко перенесенным на другое звено. ## Развитие проблемы ### Десктоп На настольных приложениях бизнес логика содержится на одном звене со всеми остальными слоями. Т.к. нет необходимости разделять слои, они зачастую перемешаны и не имеют четких границ. ### Клиент-сервер В клиент-серверном приложении имеются два звена, что приводит к созданию как минимум двух слоев. На начальном этапе сервер рассматривался только как удаленная база данных, и деление было как на рисунке – приложение на клиенте и данные на сервере. Обычно вся бизнес логика находилась на клиенте, перемешанная с остальными слоями, такими как пользовательский интерфейс. Достаточно быстро стало понятно, что можно сократить нагрузку на сеть и централизовать логику для уменьшения постоянных затрат на развертывание, перенеся большую часть бизнес логики на сервер. Архитектурно сервер был хорошо подготовленным местом в клиент-серверной системе, но база данных как платформа давала мало возможностей. Базы данных были спроектированы для хранения и выдачи и в их архитектуру не были заложены возможности расширения в направлении бизнес логики. Языки хранимых процедур в базах данных были разработаны для базовых преобразований данных, чтобы поддержать то, на что не хватало SQL. Языки хранимых процедур разработали для быстрого исполнения, а не для обслуживания сложных задач бизнес логики. Но из двух зол эта была меньшей, и часть бизнес логики переехала в хранимые процедуры. На самом деле, я готов поспорить, что бизнес логика была ужата и вбита в рамки хранимых процедур, исключительно с прагматической точки зрения. В двух звеном мире – это было не идеальным, но все-таки гораздо лучшим. ### 3-звенка Когда проблема клиент-серверной архитектуры стала явной, возросла популярность 3-х звенного подхода. Наибольшей и самой тяжелой проблемой того времени было количество подключений. Сейчас многие базы данных могут обрабатывать тысячи единовременных подключений, в девяностых большинство баз данных падали где-то на 500 подключений. Сервера зачастую лицензировались по кол-ву клиентских подключений. Это все и привело к тому, что потребовалось сократить количество подключений к базе данных. Стало популярным объединение подключений в пул, однако для реализации пула подключений в системе с множеством отдельных клиентов, необходимо внедрить третье звено между клиентом и сервером. Среднее звено так и стало называться «среднее звено». В большинстве случаев среднее звено существовало только для управления пулом соединений, но в некоторых случаях бизнес логика начала перемещаться в среднее звено потому, что языки разработки (C++, VB, Delphi, Java) гораздо лучше подходили для реализации бизнес логики, чем языки хранимых процедур. Вскоре стало очевидно, что среднее звено –это наилучшее место для бизнес логики. Также среднее звено предоставило возможность подключения клиентов с низкими скоростями, т.к. прямое соединение с базой данных, как правило, требует широкого канала и низкой задержки. ### Что такое бизнес логика? Давайте четко определим: что же такое бизнес логика. Сервер базы данных – это уровень хранения. Базы данных разработаны для хранения, получения и обновления данных с максимально высокой эффективностью. Функционал зачастую является СУПОм (Создать, Удалить, Получить, Обновить). Некоторые базы данных СУПОм и являются, но разговор не об этом. Базы данных разработаны для того, чтобы очень быстро обслуживать эти операции. Они не разработаны для форматирования телефонных номеров, рассчитывать оптимальное использование и пиковые нагрузки, определять географическое местоположение и маршруты грузов, и так далее. Хотя, я видел все это и много более сложные задачи, реализованные только с помощью или большой частью на хранимых процедурах. И все это относится не только к сложным вещам. Давайте представим себе простую задачу и такую, которую зачастую даже не относят к бизнес логике. Задача – Удалить Покупателя. Практически во всех системах удаление покупателя обрабатывается исключительно хранимой процедурой. Однако в удаление покупателя довольно многие решения должны быть приняты на уровне бизнес логики. Можно ли удалить покупателя? Какие процессы должны быть запущены до и после? Какие предосторожности должны быть соблюдены? Из каких таблиц записи должны быть удалены или обновлены в последствие? Базе данных не должно быть дела до того, что такое покупатель, она должна заботиться только об элементах, используемых для хранения покупателя. У базы данных не должно быть возможности разобраться, какие таблицы должны хранить объект покупатель, и она должна работать с таблицами не обращая внимания на объект покупатель. Задача базы данных – хранить ряды в таблицах, которые описывают покупателя. Кроме базовых ограничений вроде каскадной целостности, типов данных, индексов и пустых значений, база данных не должна иметь функционального знания о том, что же из себя представляет покупатель в бизнес слое. Хранимые процедуры, если они есть, должны оперировать только одной таблицей; исключение – это процедуры запрашивающие выборку из нескольких таблиц для выдачи данных. В этом случае, хранимые процедуры работают как представления (view). Представления и хранимые процедуры должны использоваться для консолидации значений, но исключительно для более быстрой и эффективной работы с данными в бизнес слое. ## Сегодняшние системы ### Клиент-сервер В клиент-серверных приложениях бизнес логика обычно имеется и на клиенте, и на сервере. Реальное соотношение будет меняться от приложения и компании, предыдущий пример хорошо описывает клиент-серверные приложения. Большая часть бизнес логики была реализована в хранимых процедурах и представлениях в попытке централизовать бизнес логику. Однако многие бизнес правила не могут быть реализованы просто на SQL или хранимыми процедурами, или их быстрее выполнять на клиенте, так как они основываются на интерфейсе пользователя. Из-за этих противоположных факторов бизнес логика распределена между клиентом и сервером. ### N-звенка По многим причинам при построении n-звенных систем ситуация становится только хуже в плане консолидации бизнес логики. Вместо консолидации, бизнес логика становится еще более фрагментированной. Конечно же, каждая система имеет отличия в том, как бизнес логика распределяется по слоям, но есть одно общее для всех. Бизнес логика сейчас распределяется по трем слоям вместо двух. Такая разработка имеет следующие преимущества: * Вся бизнес логика находится в одном месте и может быть легко проверенна, отлажена и изменена. * Нормальный язык разработки может быть использован для реализации бизнес правил. Такие языки более гибкие и более подходят для бизнес правил, чем SQL и хранимые процедуры. * База данных становится слоем хранения и может заниматься эффективным получением и хранением данных без ограничений относящихся к слою бизнес логики или представления. Однако, некоторое дублирование, особенно для проверки данных, должно быть и на клиенте. Эти правила должны быть поддержаны и бизнес слоем. Кроме этого, в некоторых системах отдельные высоко емкие операции, такие как пакетные обновления, могут привести к исключениям и должны быть размещены в базе данных. Потому более реалистичных подход представлен ниже. Обратите внимание, что вся бизнес логика должна быть реализована в бизнес слое, и те минимальные наборы, присутствующие в других слоях, являются просто дублями исключительно для повышения производительности или отключения тех или иных компонент пользовательского интерфейса. ## Django бизнес-логика Толстые модели (fat models), тонкие представления (thin views), тупые шаблоны (stupid templates) – один из распространенных подходов к структурированию Django приложений. Цель подхода – вынести бизнес логику из представлений и шаблонов, и поместить ее в модели. Очевидно, что представления и шаблоны не должны содержать бизнес логику, так как они имеют совсем другие обязанности. Но выносить логику в модели не лучший вариант. Это приводит к тому, что модели становятся слишком большими и имеют слишком много обязанностей. Получаются так называемые объекты боги (god objects). Из-за их сложности код сложно понять, тестировать и поддерживать. Сервисы вместо моделей Альтернатива толстым моделям – изоляция бизнес логики в сервисах (services). Сервисы – функции или классы, в которые чаще всего передаются объекты моделей (models), над которыми сервисы выполняют какие-то манипуляции в соответствии с бизнес требованиями приложения. Несколько примеров: ``` # services.py def send_confirmation_email(user): """Отправляет на почту `user` данные об активации аккаунта. :param user: объект модели User. """ def create_subscription(customer): """Подписывает `customer` на ежемесячную оплату. :param customer: объект модели Customer. """ ``` В некоторых проектах вместо services используют слово utils, что сбивает с толку, потому что модуль с именем utils не совсем подходит для хранения бизнес логики. services.py или business.py подходит больше. Однако, модуль utils подходит для хранения функций, которые не относятся к какому-то конкретному django приложению (работа с временем и датами, перевод, кеширование и т.д.) Перейдем к примерам. Представьте, что вы пытаетесь написать сайт, на котором публикуются обучающие курсы. У каждого курса есть участники: учителя и студенты. Определим модели для такого приложения: ``` # models.py from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ class Course(models.Model): title = models.CharField( max_length=80, unique=True ) # Отношение многие-ко-многим с пользователями # реализуется через модель Participation participants = models.ManyToManyField( settings.AUTH_USER_MODEL, through='Participation', related_name='courses', ) class Participation(models.Model): ROLE_STUDENT = 'ST' ROLE_TEACHER = 'TE' ROLE_CHOICES = ( (ROLE_STUDENT, _('Student')), (ROLE_TEACHER, _('Teacher')) ) user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='participations', on_delete=models.CASCADE, ) course = models.ForeignKey( 'Course', related_name='participations', on_delete=models.CASCADE, ) role = models.CharField( max_length=2, choices=ROLE_CHOICES, default=ROLE_STUDENT, ) class Meta: # Комбинация значений полей user и course # должна быть уникальна. unique_together = ('user', 'course') ``` Сервисы функции Напишем несколько простых сервисов для моделей, определенных выше: ``` # services.py from .models import Course def get_courses_in_which_user_has_been_enrolled_as_student(user): return Course.objects.filter( participations__user=user, participations__role=Participation.ROLE_STUDENT ) def get_courses_in_which_user_has_been_enrolled_as_teacher(user): return Course.objects.filter( participations__user=user, participations__role=Participation.ROLE_TEACHER ) def get_course_teachers(course): return course.participants.filter( participations__role=Participation.ROLE_TEACHER ) ``` Пример использования сервиса в представлении (view): ``` # views.py from django.shortcuts import get_object_or_404, render from .models import Course from . import services def course_teachers(request, course_pk): course = get_object_or_404(Course, pk=course_pk) teachers = services.get_course_teachers(course) return render(request, 'course_teachers.html', { 'course': course, 'teachers': teachers, }) ``` Сервисы в виде функций это хорошо, но когда нужно реализовать сервис посложнее, предпочительнее использовать классы. Сервисы классы Для создания сервисов классов рекомендуется использовать библиотеку django-service-objects. Библиотека предоставляет класс Service, который наследуется от django.forms.Form. Благодаря такому наследованию, валидность входных данных сервиса проверяется с помощью API django форм. Не нужно придумывать свой велосипед. Пример простого сервиса класса: ``` ... from django.forms.fields import ModelChoiceField from service_objects.services import Service class GetCourseTeachers(Service): course = ModelChoiceField(queryset=Course.objects.all()) def process(self): course = self.cleaned_data['course'] return course.participants.filter( participations__role=Participation.ROLE_TEACHER ) # вызов сервиса teachers = GetCourseTeachers.execute({ # Заметьте, что мы передаем `pk` объекта, а не сам объект, # иначе ModelChoiceField не будет работать 'course': course.pk }) ``` Если вы использовали django формы, то вам все это уже знакомо. Единственное отличие – есть метод process, в котором определяется бизнес логика сервиса и который вызывается сразу после успешной проверки входных данных. Если входные данные не прошли проверку, то вместо этого выбрасывается исключение service_objects.errors.InvalidInputsError Напишем сервис посложнее: добавление пользователя в участники курса в качестве студента: ``` # services.py from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied from django.forms import ModelChoiceField from service_objects.services import Service from .models import Course, Participation User = get_user_model() class EnrollAsStudent(Service): course = ModelChoiceField(queryset=Course.objects.all()) user = ModelChoiceField(queryset=User.objects.all()) def process(self): course = self.cleaned_data['course'] user = self.cleaned_data['user'] participation, created = self._get_or_create_participation(course, user) self._validate_participation(participation, created) return participation def _get_or_create_participation(self, course, user): # Получаем объект модели Participation. # Если такого не существует, создаем новый, # причем у нового объекта role = ROLE_STUDENT return Participation.objects.get_or_create( course=course, user=user, defaults={'role': Participation.ROLE_STUDENT} ) def _validate_participation(self, participation, created): if not created: if participation.role == Participation.ROLE_TEACHER: raise PermissionDenied('Already the teacher. Cannot enroll.') else: raise PermissionDenied('Already enrolled. Cannot re-enroll.') ``` Данный сервис легко расширяется. Добавим отправление уведомления на почту пользователя сразу после того, как пользователь стал участником курса: ``` class EnrollAsStudent(Service): ... def __init__(self, *args, mailer=None): self._mailer = mailer def process(self): ... self._notify_user_about_enrollment(course, user) return participation def _notify_user_about_enrollment(self, course, user): if self._mailer is not None: self._mailer( subject='New enrollment', body='You have been enrolled to study the course: {}'.format( course.title ), from_='Example <admin@example.com>', to=user.email ) ``` В серсис EnrollAsStudent внедряется объект mailer, который отвечает за отправку сообщения на электронную почту. Если кратко, то внедрение зависимостей позволяет создавать объекты со слабой связью (low coupling), которые легко тестировать и повторно использовать. Сервисы функции & сервисы классы Для простых сервисов – функции, для сервисов посложнее – классы. Однако плохо то, что интерфейсы вызовов у функций и у классов разные: ``` # вызов функции services.get_course_teachers(course) # вызов класса services.EnrollAsStudent.execute({ 'course': course.pk, 'user': user.pk }) ``` Проблема в том, что при вызове сервиса в представлении, шаблоне или где-либо еще, мы должны знать, какой это сервис (функция или класс). Это неудобно, и такие лишние знания приводят к проблемам. Например, если функция расширяется, и мы хотим переписать эту функцию в класс, то нам придется поменять код везде, где эта функция вызывалась, потому что у класса и у функции разные интерфейсы вызовов. Решение такой проблемы: обернуть сервис класс в функцию фабрику: ``` def enroll_as_student(course, user): return EnrollAsStudent.execute({ 'course': course.pk, 'user': user.pk }) ``` Теперь интерфейсы вызовов одинаковые. Еще один плюс использования функций фабрик – проще внедрять зависимости в сервис, не раскрывая все детали пользователю: ``` def enroll_as_student(course, user, mailer=None): mailer = mailer if mailer is not None else SomeDefaultMailService return EnrollAsStudent.execute({ 'course': course.pk, 'user': user.pk }, mailer=mailer) ```