678,12
Рейтинг
OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов
24 марта

«Откуда ноги растут» или что предшествует программированию?

Блог компании OTUS. Онлайн-образованиеПрограммированиеИстория IT
Всем привет! На днях в рамках образовательной платформы OTUS запускается новый курс: «Архитектура и шаблоны проектирования». В связи со стартом мы провели традиционный открытый урок. На нём изучили особенности монолитного приложения, многоуровневой и бессерверной архитектур. Подробно рассмотрели систему, управляемую событиями, сервис-ориентированную систему и микросервисную архитектуру.



Преподаватель — Матвей Калинин, специалист с более чем 20-летним опытом программирования и автор курса «Архитектура и шаблоны проектирования».

Небольшая предыстория


Изначально программы действительно решали поставленную перед собой задачу и были довольно обособленными. Но со временем программы росли, и люди стали понимать, что возникающая сложность функционала начинает влиять на скорость доработок, надежность и устойчивость к разного рода усложнениям.

Действительно, когда у нас одна-две программы, которые одинаковы и не меняются, написать эти программы и обеспечить их взаимодействие несложно. Но когда их всё больше и больше, проблем не избежать, и не важно, о каком комплексе программного обеспечения идёт речь.

Сегодня приложения, в большинстве своём, являются распределенными. Они состоят из нескольких модулей и связаны между собой системными сообщениями. То есть получается довольно большой конгломерат программ, взаимодействующих между собой. А чтобы они взаимодействовали успешно, нам необходимо учитывать:

  • быстроту реагирования;
  • пропускную способность;
  • производительность в пересчете на одну единицу ресурса;
  • способность к масштабированию;
  • возможности интеграции;
  • особенности используемых платформ;
  • особенности существующих бизнес-процессов;
  • и многое другое…

В то же самое время нельзя не вспомнить следующую цитату:

«Правильный программный код не требует больших трудозатрат на свое создание и сопровождение. Изменения вносятся легко и быстро. Ошибки немногочисленны. Трудозатраты минимальны, а функциональность и гибкость — максимальны».

Роберт Сесил Мартин

То есть, написав программу в идеале один раз (если она хорошо написана), доработки будут минимальны. Как же этого добиться и увязать одно и другое? Чтобы ответить на этот вопрос, обратимся к истории.

  • 1968 год: в научно-исследовательской работе Эдсгера Дейкстры была описана многопрограммная система, в которой все действия разделены на ряд последовательных процессов («The Structure of the «THE»-Multiprogramming System»). Эти последовательные процессы размещены на различных иерархических уровнях, на каждом из которых была реализована одна или несколько независимых абстракций. Были затронуты даже вопросы тестирования!
  • 1970 год: Дэвид Парнас в своей статье «On the Criteria To Be Used in Decomposing Systems into Modules» описал подход разделения на модули как механизм для улучшения гибкости и понятности системы, который позволяет сократить время разработки. Эффективность подхода зависит от критериев, используемых при делении системы на модули. Структура системы программного обеспечения стала приобретать важное значение;
  • 1996 год: Мэри Шоу и Дэвид Гэрлан из университета Carnegie Mellon написали книгу под названием «Архитектура программного обеспечения: перспективы новой дисциплины» («Software Architecture: Perspectives on an Emerging Discipline»), в которой выдвинули концепции архитектуры программного обеспечения, такие как компоненты, соединители (connectors), стили и т. д.;
  • 2007 год: в этом году вышел первый стандарт программной архитектуры — IEEE 1471: ANSI / IEEE 1471 — 2000: Рекомендации по описанию преимущественно программных систем. Он был принят под названием ISO ISO / IEC 42010:2007.

Какова цель архитектуры ПО?


Итак, мы поняли, что нужно правильно структурировать программный продукт. Но какова же цель архитектуры ПО? И вообще, почему в названии этой статьи мы использовали словосочетание «растут ноги»?

Всё дело в том, что когда вы начинаете программировать, вы уже знаете, что хотите построить. Изначально всё начинается с бизнес-процесса. Есть заказ и заказчик, который описывает (хотя бы словами), как должна работать его система. И вот эта система поначалу существует только в описанном им виде. Потом этот процесс формализуют и обрисовывают, но это ещё полдела, т. к. дальше наступает разработка. А разработчику нужно этот процесс преобразовать в программный продукт, который должен…

Тут самое время вспомнить ещё одну цитату:

«Цель архитектуры программного обеспечения — уменьшить человеческие трудозатраты на создание и сопровождение системы».

Роберт Сесил Мартин


Не следует удивляться, что цель сформирована такими общими фразами. Дело в том, что архитектура живёт в абстрактных идеях. Почему? Потому что человек, который занимается архитектурой ПО, преобразует видение бизнес-заказчика в видение разработчика. И если мы говорим об архитекторе команды разработчиков и об Enterprise-архитекторе, то у каждого из них разная цель, но оба они стремятся к одному — уменьшить человеческие трудозатраты.

В таком контексте интересно посмотреть на ценности программного обеспечения:

  • код удовлетворяет требованиям бизнес-процесса;
  • есть возможность быстрого изменения поведения.

И тут напрашивается вопрос: а что важнее, работа системы или простота ее изменения? Чтобы ответить на него, давайте посмотрим на программу с точки зрения полезности:

Если есть правильно работающая программа, которая не допускает изменений, то такая программа, со временем, станет неактуальной — когда изменятся требования.

Если же программа работает неправильно, но изменения в ней можно сделать, то её можно заставить работать правильно в рамках изменяющихся требований. Эта программа будет оставаться полезной всегда.

Парадигмы (модели) программирования


Как вы думаете, какая самая известная (самая первая) модель программирования? Разумеется, монолит. Здесь уместно снова на миг вернуться в 1968 год и вспомнить Эдсгера Дейкстра, который показал, что безудержное использование переходов (инструкций goto) вредно для структуры программы. Он предложил заменить переходы более понятными конструкциями if/then/else и do/while/until.

Сейчас инструкции goto можно увидеть не так часто. Но раньше инструкции goto были очень распространены. Вообще, это форменное зло, ведь когда вы видите код, в котором есть инструкция goto, возникает ощущение, что можно не найти тот самый пункт, куда он уходит. И чем больше goto, тем сложнее, то есть мы получаем спагетти-код. Это сейчас мы называем «спагетти» тот код, где, к примеру, 20 вложенных if’ов и, возможно, один goto. Это тоже не особо понятный код. Представьте, что у вас 10-15 goto, и вы пытаетесь понять, как работает цикл — вот что имел в виду Дейкстра.

Спагетти-код — слабо структурированная, запутанная и трудная для понимания программа, содержащая много операторов goto (особенно, переходов назад), исключений и других конструкций, ухудшающих структурированность. В общем, хорошо известный и довольно распространённый антипаттерн программирования. Такая программа отнимает много времени на понимание, поддержку и тестирование.

Структурное программирование


Существовали языки программирования, они реализовывали какие-то цели. До определённого момента структура программ не сильно влияла сильно на реализацию. Но программы росли, между ними образовывались многочисленные связи. И в один прекрасный момент человек ощутил, что алгоритм важно упаковывать в структуру, которая легко читается, тестируется. Начались изменения на уровне структуры программы. То есть уже не только сама программа должна была удовлетворять результату, но и структура программы также должна была соответствовать какому-то критерию.

Таким образом, мы плавно перешли к структурному программированию. Согласно ему, программа строится без использования оператора goto и состоит из трёх базовых управляющих структур:

  • последовательность,
  • ветвление,
  • цикл.

Используются подпрограммы, сама разработка ведётся пошагово, методом «сверху вниз».
И снова вернёмся в 1966-й… В этом году Оле-Йохан Даль и Кристен Нюгор заметили, что в языке ALGOL есть возможность переместить кадр стека вызова функции в динамическую память (кучу), благодаря чему локальные переменные, объявленные внутри функции, могут сохраняться после выхода из нее. В результате функция превратилась в конструктор класса, локальные переменные — в переменные экземпляра, а вложенные функции — в методы. Это привело к открытию полиморфизма через строгое использование указателей на функции.

Объектно-ориентированное программирование


Как вы все знаете, в ООП программы представляются в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.

Основные принципы структурирования:

  • абстракция;
  • наследование;
  • полиморфизм.

Можно посмотреть на эти все принципы с другой стороны. Роберт Мартин разработал принципы SOLID, которые, с одной стороны, определяют, как именно программисту работать с абстракциями, а с другой стороны, формируют сам процесс полиморфизма, наследования.

Императивное программирование


Императивная программа похожа на приказы, которые должен выполнить компьютер. Для таких программ характерно:

  • в исходном коде программы записываются инструкции;
  • инструкции должны выполняться последовательно;
  • данные, получаемые при выполнении предыдущих инструкций, могут читаться из памяти последующими инструкциями;
  • данные, полученные при выполнении инструкции, могут записываться в память.

Тоже очень древняя конструкция. Основные черты императивных языков:

  • использование именованных переменных;
  • использование оператора присваивания;
  • использование составных выражений;
  • использование подпрограмм.

Однако продолжаем «путешествие во времени». На этот раз вернемся на мгновение в 1936(!) год. Он интересен тем, что именно в этом году Алонзо Чёрч изобрел лямбда-исчисление (или λ-исчисление), которое позднее, в 1958 году, легло в основу языка LISP, изобретенного Джоном Маккарти. Основополагающим понятием λ-исчисления является неизменяемость — то есть невозможность изменения значений символов.

Функциональное программирование


Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных и результатов других функций и не предполагает явного хранения состояния программы.

Фактически, это означает, что функциональный язык не имеет инструкции присваивания.

Посмотрим разницу между императивным и функциональным стилями на примере:

# императивный стиль
target = []  # создать пустой список
for item in source_list:  # для каждого элемента исходного списка
    trans1 = G(item)  # применить функцию G()
    trans2 = F(trans1)  # применить функцию F()
    target.append(trans2)  # добавить преобразованный элемент в список
# функциональный стиль
compose2 = lambda A, B: lambda x: A(B(x))
target = map(compose2(F, G), source_list)

Так что же такое архитектура программного обеспечения?


Архитектура программного обеспечения есть совокупность решений об организации программной системы.

Она включает в себя:

  • выбор структурных элементов и их интерфейсов;
  • поведение выбранных элементов и интерфейсов, их взаимодействие;
  • соединение выбранных элементов структуры и поведения в более крупные системы;
  • архитектурный стиль, который направляет всю организацию.

Обратите внимание: сначала мы пришли к тому, что goto нас не устраивает, потом мы увидели, что есть определенные правила (инкапсуляция, наследование, полиморфизм), дальше мы поняли, что эти правила работают не просто так, а в соответствии с определенными принципами. Четвертый момент — это уже архитектурный стиль, и о нём мы поговорим далее.

Главное предназначение архитектуры — поддержка жизненного цикла системы. Хорошая архитектура делает систему легкой в освоении, простой в разработке, сопровождении и развертывании. Конечная цель — минимизировать затраты на протяжении срока службы системы и максимизировать продуктивность программиста, а если быть точнее, команды разработчиков.

Сначала мы говорили, какие правила нам нужны для написания программ. Но, помимо написания программ, есть еще сопровождение, разработка и развертывание. То есть архитектура захватывает не какую-то область программирования, а весь цикл разработки.

Хорошая архитектура должна обеспечивать:

  1. Разнообразие вариантов использования и эффективную работу системы.
  2. Простоту сопровождения системы.
  3. Простоту разработки системы.
  4. Простоту развертывания системы.

Архитектурные стили


Монолит


В первую очередь, поговорим про всем известный монолит. Этот стиль ещё встречается, правда, в небольших системах. Монолитная архитектура означает, что ваше приложение — это большой связанный модуль. Все компоненты спроектированы так, чтобы работать вместе друг с другом, имеют общую память и ресурсы. Все функции или основная их часть сосредоточены в одном процессе или контейнере, который разбивается на внутренние слои или библиотеки.



Достоинства:

  1. Легко реализовать. Не надо тратить время на размышления о межпроцессном взаимодействие.
  2. Легко разработать сквозные тесты.
  3. Простота развертывания.
  4. Простое масштабирование за счёт Loadbalancer перед несколькими экземплярами вашего приложения.
  5. Простота в эксплуатации.



Но недостатков сейчас у него больше:

  1. Сильная связанность приводит к запутанности с эволюцией приложения.
  2. Независимое масштабирование компонентов приводит к усложнению и полному ретесту функционала.
  3. Сложнее для понимания.
  4. С ростом сложности растет время разработки.
  5. Отсутствие изоляции компонентов.

С одной стороны, монолит хорош, но как только начинаешь его развивать, возникают сложности.

Что такое сервис?


Сейчас все знают, что такое сервис. Его можно определить как видимый ресурс, выполняющий повторяющуюся задачу и описанный внешней инструкцией.

Современные сервисы имеют следующие особенности:

  • сервисы ориентируются не на возможности ИТ, а на нужды бизнеса;
  • сервисы самодостаточны и описываются в терминах интерфейсов, операций, семантики, динамических характеристик, политик и свойств сервиса;
  • повторное использование сервисов обеспечивается их модульным планированием;
  • сервисные соглашения заключаются между сущностями, именуемыми поставщиками и пользователями, и не влияют на реализацию самих сервисов;
  • в течение своего жизненного цикла сервисы размещаются и становятся видимы благодаря сервисным метаданным, реестрам и хранилищам;
  • агрегация: на слабо связанных сервисах строятся объединяющие бизнес-процессы и сложные приложения для одного или нескольких предприятий.

В результате вышеперечисленных особенностей возникло такое понятие, как сервис-ориентированная архитектура (SOA).

Сервис-ориентированная архитектура (SOA)


SOA — архитектурный стиль для создания ИТ-архитектуры предприятия, использующий принципы ориентации на сервисы для достижения тесной связи между бизнесом и поддерживающими его информационными системами.



SOA обладает следующими характеристиками:

  1. Улучшает взаимосвязь между архитектурой предприятия и бизнесом.
  2. Позволяет из наборов интегрированных сервисов создавать сложные приложения.
  3. Создает гибкие бизнес-процессы.
  4. Не зависит от набора технологий.
  5. Автономна в смысле независимой эволюции и развертывании.

Модель развертывания SOA состоит из бизнес-анализа и разработки и ИТ-анализа и разработки. Сборка состоит из программирования сервисов и построения сложных приложений. Размещение состоит из размещения приложений и средств времени исполнения, таких как Enterprise Service Buses (ESB). Что касается руководства, то оно состоит из поддержки операционной среды, мониторинга производительности сервисов и слежения за соблюдением сервисных политик.



Микросервисная архитектура


Пришло время поговорить и про микросервисную архитектуру. В ней приложение состоит из маленьких независимых приложений-сервисов, каждый со своими собственными ресурсами. Сервисы взаимодействуют друг с другом для выполнения задач, относящихся к их бизнес-возможностям. Существует несколько единиц развертывания. Каждый сервис развертывается самостоятельно.



Достоинства:

  1. Поддерживает модульность всей системы.
  2. Несвязанные сервисы проще дорабатывать для обслуживания различных приложений.
  3. Разные сервисы могут принадлежать разным командам.
  4. Услуги сервиса могут быть повторно использованы всей компанией.
  5. Легче понять и протестировать.
  6. Не привязаны к технологии, используемой в других сервисах.
  7. Изолированность сервиса повышает общую надёжность всего функционала.



Недостатки:

  1. Сложности с реализацией в общей функциональности (логирование, права доступа и пр.).
  2. Сложно проводить сквозные тесты системы.
  3. Сложнее эксплуатация и поддержка.
  4. Требуется больше оборудования, чем для монолита.
  5. Поддержка несколькими командами приводит к координации взаимодействия между ними.

Стоит отметить, что в этой архитектуре очень сложно что-либо сделать без DevOps.

Многоуровневая архитектура


Многоуровневая архитектура – это наиболее распространенный шаблон архитектуры. Его ещё называют n-tier архитектурой, где n – количество уровней.

Система делится на уровни, каждый из которых взаимодействует лишь с двумя соседними.

Архитектура не подразумевает какое-то обязательное количество уровней — их может быть три, четыре, пять и больше. Чаще всего используют трехзвенные системы: с уровнем представления (клиентом), уровнем логики и уровнем данных.
Наиболее часто встречаются следующие слои:

  • слой представления (для работы с пользователями);
  • слой приложения (сервиса — безопасность, доступ);
  • слой бизнес-логики (реализация предметной области);
  • слой доступа к данным (представление интерфейса к БД).

Закрытые слои


Концепция изоляции уровней строго отделяет один уровень от другого: перейти с одного уровня можно только на следующий и перескакивать сразу через несколько нельзя.



Открытые слои


Система позволяет перепрыгивать через открытые уровни и попадать на те, что расположены ниже.



MVC


Концепция MVC была описана в 1978 году. Окончательная версия концепции MVC была опубликована лишь в 1988 году в журнале Technology Object.

Основная цель — в отделении бизнес-логики (модели) от её визуализации (представления, вида). Что это даёт:

  • повышается возможность повторного использования кода.
  • пользователь может видеть те же самые данные одновременно в различных контекстах.



Модель предоставляет данные и реагирует на команды контроллера, изменяя своё состояние. Представление отвечает за отображение данных модели пользователю, реагируя на изменения модели. Контроллер интерпретирует действия пользователя, оповещая модель о необходимости изменений.



Архитектура, управляемая событиями


Ещё одна интересная архитектура. Она применяется при разработке и реализации систем, передающих события среди слабосвязанных программных элементов.

Состоит из разрозненных одноцелевых компонентов обработки событий, которые асинхронно получают и обрабатывают события.

Шаблон состоит из двух основных топологий — посредника и брокера.

Топология посредника


Есть процессы, где нужен контроль за последовательностью этапов. Здесь нам пригодится посредник.

Основные типы компонентов архитектуры:

  • очереди событий;
  • посредник событий;
  • каналы событий;
  • обработчики событий.

Событие

Событие можно определить как «существенное изменение состояния». Событие может состоять из двух частей:

  • заголовка (имя события, временную метку для события, и тип события);
  • тела (описывает то, что в действительности произошло).

Топология посредника

Клиент отправляет событие в очередь событий (event queue), которая используется для передачи события посреднику (mediator).

Посредник (mediator) получает начальное событие и отправляет дополнительные асинхронные события в каналы событий (event channels) для выполнения каждого шага процесса.

Обработчики событий (Event processors), которые прослушивают каналы событий, получают событие от посредника и выполняют определенную бизнес-логику, обрабатывая событие.



Топология брокера

Топология брокера отличается от топологии посредника тем, что отсутствует центральный посредник событий. Поток сообщений распределяется по компонентам процессора событий в виде цепочки через брокера облегченных сообщений (например, ActiveMQ, HornetQ и т. д.). Эта топология полезна, когда есть относительно простой поток обработки событий и нет нужды в централизованной оркестровке событий.

Каждый компонент обработчика событий (event-processor) отвечает за обработку события (event) и публикацию нового события, указывающего только что выполненное действие.



Если первая ситуация была асинхронна «где-то внизу», то вторая ситуация асинхронна, можно сказать, совсем. Одно событие порождает несколько событий, и они могут увеличиваться и увеличиваться.

Достоинства архитектуры, управляемой событиями:

  • компоненты изолированы и позволяют делать доработку каждого без влияния на остальные части системы;
  • простота развертывания;
  • высокая производительность. Позволяет выполнять параллельные асинхронные операции;
  • хорошо масштабируется.

Недостатки:

  • сложно тестировать;
  • сложно развивать из-за ярко выраженной асинхронности.

Бессерверная архитектура


Это способ создания и запуска приложений и сервисов без необходимости управления инфраструктурой. Приложение по-прежнему работает на серверах, но управление этими серверами платформа полностью берет на себя.

Вся инфраструктура поддерживается сторонними провайдерами, а необходимая функциональность предлагается в форме сервисов, отвечающих за процессы аутентификации, передачи сообщений и пр.

Выделяют следующую терминологию:

  1. Функция как услуга (Function-as-a-Service) — модель, дающая возможность вызова экземпляра управляющего кода без необходимости управления серверами и серверным приложением.
  2. Бэкенд как услуга (Backend as a Service) — модель, позволяющая разработчикам веб-приложений и мобильных приложений связать их приложения с серверным облачным хранилищем и API, выставляемыми серверными приложениями. Предоставляет такие функции, как управление пользователями, извещающие уведомления, интеграция со службами социальных сетей.

Если рассматривать архитектуру «Клиент-сервер», то большая часть логики в системе (аутентификация, навигация по страницам, поиск, транзакции) реализуется серверным приложением.



В бессерверной архитектуре всё несколько иначе. Аутентификация заменена сторонней службой BaaS (готовый облачный сервис), доступ к БД также заменен другой службой BaaS. Логика приложения частично уже внутри клиента — например, отслеживание сеанса пользователя.

Клиент уже на пути к тому, чтобы стать одностраничным приложением. Поиск можно делать через услугу поиска (FaaS — функция как услуга). Функция покупки тоже изолирована от клиента отдельной услугой (FaaS).



Что же, на этом всё, если интересуют подробности, смотрите видео целиком. Что касается нового курса, то ознакомиться с его программой можно здесь.
Теги:архитектурапаттерныпрограммирование
Хабы: Блог компании OTUS. Онлайн-образование Программирование История IT
+8
4,6k 54
Комментарии 3
Похожие публикации
Лучшие публикации за сутки
Информация
Дата основания

1 мая 2017

Местоположение

Россия

Сайт

otus.ru

Численность

31–50 человек

Дата регистрации

22 марта 2017

Блог на Хабре