Привет всем, меня зовут Юра.
Сегодня я хотел бы поделиться с вами своим опытом и идеями в сфере проектирования и разработки ПО, развеять некоторые предрассудки и пролить свет на текущее состояние SOA решений в нашем, не совсем "реактивном", мире. О том, как писать меньше кода, получать меньше недовольных клиентов, седины, и больше профита. Реализовывать полностью реюзабельные решения без побочных эффектов, и почему до сих пор этого никто не сделал ⊙.☉
(ง︡'-'︠)ง И так, Приветствую. Спасибо что решили уделить мненемного свободного, и не очень, времени.
Постараюсь изложить всё кратко и понятно, хотя последнее у меня, обычно, хреново получается.
Пару лет назад я столкнулся с проблемой нехватки инструментов для реализации бэкенд и фронтенд решений (вэб, десктоп, мобильное). С того времени много жидкости утекло, я ознакомился с большим количеством существующих подходов и у меня возникли идеи как всё сделать чуть лучше чем оно есть сейчас. Изучив текущее положение на рынке, я понял что существующие решения специально не дают полного контроля над приложениями и являются либо пустышками, которые сами по себе лишь маленькая часть всей архитектуры проектов; их целью является поддержание интереса клиентов и экспертной аудитории, что позитивно сказывается на стоимости конторы, либо наркотиком поддержки — многослойный ужас с различными мутантами спецификаций OASIS'a, где на поддержку и тренинг персонала нужно нехило отлистать. Наиболее ярким примером последнего является стэк WSO2. Я считаю его наиболее полным SOA решением на рынке, хоть и монструозным, кривым и очень избыточным. Подобные вещи сложно сопоставить Agile методологии, так как они все крутятся вокруг бумаг и бизнес-процессов, а не вокруг потребностей живых людей (первый пункт Agile манифеста). В этих статьях я хочу поделиться своим видением проектирования SOA, как его адаптировать для требований современных рынков и RAD'a, описать основные преимущества подобных подходов.
Сейчас очень модно обсуждать различные реактивные веяния — что системы должны быть:
Картинкасворована с реактивного манифеста. Там Elastic и Resilient, наверное, потому что Scalable и Fault tolerant — понятия слишком популярные и не представляют уже такой маркетинговой ценности, как во времена MongoDB. Особенно ярко это всё фигурирует в контексте так называемого «функционального реактивного программирования» (FRP).
На практике получается так что всё это сплошная маркетинговая муть для привлечения внимания инвесторов, и, в общем то, большая часть решений не представляет какой либо практической ценности, так как всё эту «реактивность» разработчики должны реализовывать своими силами, без каких либо готовых решений или наработок по существующим требованиям. Даже полноценного ААА нигде нет.
Другое дело SOA — там это всё уже должно работать прям с коробки, но как бы не так.
Вот получается так, что у каждого вендора своё SOA с шахматами и поэтессами, и друг с другом они не дружат, хотя с OpenSource решениями всё хорошо, об этом далее. Это всё достаточно быстро устаревает, как-то совсем не формируются новые сообщества, и проекты разрабатываются лишь усилиями их владельцев. Если это SOA, и любой сервис можно использовать хоть 100500 раз в целой пачке проектов, то почему у нас до сих пор нет стандартизированного репозитория реюзабельных сервисов и доменов? В этом просто не заинтересованы владельцы существующих решений — ведь нужно же денег с поддержки высасывать.
Особенно весело обсуждают последний год микросервисы. Я считаю что это довольно таки дурацкое название, и совсем они не «микро», так как подразумевают глубокую инкапсуляцию нескольких протоколов. По этому выполнять «микросервисы» в рамках одного сервера, или виртуальных машин, — лишь увеличивать задержки да вакуум масштабировать. А вообще, в классическом SOA совсем без разницы является ли сервис частью единого системного процесса, или он должен быть запущен в виде отдельного наборов процессов со своими очередями и протоколами на разных машинах — всё это лишь вопрос регистрации сервиса и его зависимостей. Так что я считаю «микросервисы», тоже, не более чем маркетинговой уловкой — попыткой «переосмысления» очевидных старых идей, для того что бы выудить побольше денег.
«Ok, а что не так с MVC ?» — наверняка, задумаетесь вы… а ничего, оно есть, оно для простых графических интерфейсов, без наворотов, и в неумелых руках даже банальный CRUD — это тонна копи-пасты. MVC плохо контролирует предметную область, поощряет хранение состояния и дублирование кода, в нём плохо разделены обязанности логики (сегрегации команд и запросов), поощряется денормализация модели. Другое дело велосипеды: ADR, всякие ECB, EBI с Clean Architecture и прочие вариации на тему Hexagonal Architecture — они являются попыткой переработать MVC, конечно не без своих индивидуальных недостатков и, естественно, не у всех хватит опыта и навыков для реализации подобных вещей, но в них решено довольно много проблем присущих MVC. Всё это создано, по большей части, за последние 3 года PHP-адептами и соответсвенно, так или иначе, отражаются особенности этой платформы. Не то что бы я очень нелюбил РНР, но современные вариации, в куче с Python'ом и Ruby, не позволяют полностью удовлетворить требования современных рынков.
Я понимаю что могу задеть чувства адептов культа "Золотого молота", но далее будет понятно почему всё так совсем не оптимистично, и покажу что придумал на этот счёт.
А есть ещё Flux, но это тоже ещё одна, очередная, маркетинговая уловка.
Flux в простонародье — ни что иное как более сложная аббревиатура CQRS-ES, потому Facebook и хотели упростить — что бы можно было привлечь внимание и плодитьхомячков фанатов, развивать довольно посредственные сообщества, и это не более чем PR. Цель Facebook: не предоставить качественный инструмент для реализации ваших проектов, а передать идею сообществу пользователей, что бы они лепили как можно больше костылей на GitHub'e.
Именно CQRS-ES паттерны является основой всех «реактивностей» и наиболее хорошо подходит для реализации «реактивных» приложений, но в чистом виде он бесполезен. Я считаю что проблемы использования и преимущества CQRS-ES наиболее хорошо раскрываются в контексте проектирования SOA.
Главной проблемой современного SOA, да и вообще всех существующих архитектур, является довольно плохое абстрагирование предметной области и определение доменной модели. Я рассмотрю этот вопрос с точки зрения синхронизированных дискретных конечных автоматов FSM и покажу как можно более корректно (без избыточности) определять домены, учитывая современные требования к приложениям.
Как говорил Эванс:
В конце жизненный цикл разработки бэкенда и фронтенда должен выглядеть вот так:
Остаётся лишь дизайн (по желанию) и реализация недостающего функционала, посредством добавления своих сервисов и доменов в систему.
Это как будто если бы у вас был бы свой карманный Firebase, который кроме того что автоматом вносил бы все правки и миграции в базу, ещё бы и производил Scaffolding для ваших мобильных, десктоп и вэб приложений: автоматически генерировал все представления, шаблоны, валидацию и клиенты к API c Push нотификациями, и асинхронным UI.
Из-за того что большая часть любого современного функционала очень шаблонна — нет смысла от проекта к проекту переписывать всё каждый раз «с нуля». Нужна платформа для того что бы люди могли поделится своими наработками и стандартизировать контроль качества и взаимодействие между ними. Таким образом можно получить сообщество пользователей которое неявно мотивируется к повышению качества продукции и квалификации его членов, добавим сюда геймификацию с повышением уровней, ачивками и шуточными статами… ну вы поняли — фантазии есть где разгуляться.
При разработке подобного решения нужно предотвратить возникновение двух проблемных ситуаций — это «разработка комитетом» и «разработка по спецификациям», что собственно привело к ужасающему раздуванию J2EE стека.
Сейчас почти никто не заинтересован в автоматизации труда разработчиков, хотя большая часть кода существующих приложений может быть сгенерирована автоматически или абстрагирована средствами динамического программирования.
Да… если говорить очень грубо и упрощённо: по схеме БД можно сразу сгенерировать REST эндпоинты, клиент API и шаблоны для вашего фронта на любимом Angular'e / React'e / Ember'e и т.п. Хоть я и не очень перевариваю подходы современных браузерных фреймворков, об этом далее. Остаётся прописать группы пользователей и их права, натянуть существующий дизайн. Если вы считаете подобное мистикой — ну вот Tastypie уже умеет генерировать REST эндпоинты с модельки, правда там не хватает фич и производительность довольно посредственная, даже для Python'a, из-за плохого дизайнa, но в качестве примера его использовать можно.
Это идея. Она состоит в том что приложения должны состоять из реюзабельных взаимозаменяемых «чёрных коробочек» — сервисов, которые общаются по стандартизированным протоколам и интрефесам между собой.
Есть несколько важных условий, сервисы:
В принципе, по первым трём условиям должно быть более-менее понятно. Сервисы не зависят друг от друга, не разделяют общих ресурсов, и могут мигрировать между процессами как локальной так и удалённых машин. Могут быть повторно и одновременно использованы в нескольких проектах.
По поводу состояний — всё просто: сервер не должен хранить данных о пользовательской сессии, это делается для того что бы запрос мог быть обработан любым экземпляром запущенного сервиса, обычно наименее загруженным в данный момент времени. Это делается посредством внедрения шифрованной, «просоленной» информации о сессии в пользовательский запрос. Для того что бы не передавать эту полезную нагрузку при каждом запросе — обычно создают отдельный сервис кратковременной регистрации пользовательских сессий, который временно их кэширует. Создание глобального сервиса для хранения пользовательских сессий приведёт к появлению «бутылочного горлышка» в котором ещё нужно будет решить задачу консенсуса чтоочень негативно скажется на скорости обработки запросов.
С абстракциями всё довольно туго — те или иные домены (схема базы и логика), должны быть динамически расширяемы в зависимости от конкретных потребностей зависимых сервисов и доменов. Допустим, существует сервис учёта (Accounting) и в нём зарегистрированы различные услуги — для некоторых в тарификации предусмотрена только постоплата, другие же требуют предоплаты. Добавим сюда ещё различных скидочных и накопительных систем и получаем качественную головную боль на многие месяцы, потому что вся логика работы этих компонент постоянно меняется. Большую роль здесь играет нормализация реляционной модели — прошу заметить что нормальных форм уже давно шесть. Хорошее абстрагирование и детализация предметной области позволяет уменьшить сложность внедрения новой, или изменения существующей, логики, но об этом в следующей части.
Возможность разделения доступа к сервисам между несколькими «подчинёнными» и «руководителями» следует из требования к слабой связности и отсутствии разделяемых состояний. Всё прям как с многопоточным программированием и методами предотвращения состояний «гонки». Отдельно стоит упомянуть случай расширения доменов: там существует проблема совместимости при совместном использовании расширенного домена несколькими сервисами. В общем, грубо-говоря, когда одному сервису нужна табличка с одним количеством полей и состояний (ну там, значение перечисления), а другому — с совсем другим, и как их «подружить» что бы не было проблем при взаимном использовании этой таблички, или какого-то другого ресурса.
Есть много системных «корневых» сервисов которые отвечают за работу всех остальных, и за их поведение в случае возникновения ошибок. Одним из них является Регистратор который позволяет регистрироватькэп и запускать новые экземпляры сервисов, прогонять тесты внедрения и корректности, откатываться на предыдущие версии в случае возникновения ошибок. Прям как в Erlang'e :3 Вообще SOA во многом очень похожа на модель OTP, правда там тоже многого не хватает.
Думаю, на этой ноте можно завершить обзор задач и требований SOA.
По потребности что-то сюда ещё добавлю.
Вот диаграмма как я вообще представляю структуру сервис-ориентированной архитектуры.
Давайте рассмотрим предназначение корневых сервисов.
Каждый из этих сервисов имеет свои особенности дизайна (программного) и я постараюсь описать все детали в следующих статьях, надеюсь ничего не упущу.
Любой проект должен содержать, как минимум, Регистратор, Архивариус, Менеджер Ресурсов и ААА, хотя вы, наверняка, можете придумать названия и получше. Два последних упускают по ряду причин связанных со сложностью реализации расширяемых доменов (привет кокаин).
Полноценного SOA сейчас нет — везде где-то чего-то не хватает.
О кодогенерации по гипермедиатипам история вообще умалчивает.
Я работал с
Конечно в случае с J2EE стеком наблюдается ужасающая избыточность и для высоких нагрузок ни WSO ни JBoss не годятся. Да и в общем «слабое связывание» не шибко вяжется к современным слоёным j2ee EJB плюшкам.
Cocaine и Bluemix я, лично, отношу к SOA решениям лишь частично — нет готовой системы внедрения и стандартизации взаимодействия существующих серверов, полный DIY. В случае с кокаином — есть locator, хоть и довольно рудиментарный, но хорошо что хоть что-то. В bluemix можно реализовать неплохое SOI, но тоже нужно поиграться чуток. С точки зрения SOI наиболее гибким решением являются сервисы Amazon.
Все вендоры предлагают разворачивать инфраструктуру только на своих PaaS'ах, особенно это заметно в случае с Oracle и её Integration Cloud Service. Нужно понимать что большинству пользователей SOA решений нужно что бы все сервера находились в пределах одного датацентра, а задержки при коммуникации были минимальны — часто приходится своими силами организовывать CDN'ы и прочее распределённое барахло, а лишний vendor lockin при выборе датацентра лишь связывает руки. Уже и Amazon и Azure отваливались — серьёзным и ответственным клиентам лучше производить менеджмент рисков самостоятельно, а для простых обывателей хватит и «запустили, работает — пошлипить отдыхать и ждать пока отвалиться, начнутся звонки в 4 утра...», обычно в 95% случаев так оно и происходит. Да и дешевле снимать десяток лысых дедиков с самодельным SOI на комнить digital ocean, linode, online.net или ovh и жонглировать ими по необходимости.
BPEL/BPMN — отдельная тема: попытались написать язык описания бизнес процессов для секретарш, а получили многоликое непонятно что — то что не предусматривается стандартом реализовать без правок самой спецификации языка почти невозможно. Для этого нужно связываться с OASIS'ом, вступать в консорциум, выпускать RFC… ну вы поняли — ничего общего непосредственно с решением поставленных задач, а лишь плата за существования многоликих монолитных стандартов, которые невозможно адаптировать для современных требований рынка в приемлемых временных рамках. Проще, на много проще, брать и писать всё руками… на любом любимом языке. Описание бизнес-процессов можно очень упростить, если рассматривать его с точки зрения конечных автоматов (FSM).
Главным преимуществом существующих решений является гарантии со стороны вендоров, которых достаточно для использования в мед. секторе и частично в забугорном гос. и automotive секторе — там нет таких жёстких требований рынка и там не нужно следовать «последним веяниям моды». Следовательно для реализации новых проектов, или даже, не побоюсь этого слова, стартапов — эти решения и платформы не годятся.
Также, как полноценное SOA можно рассматривать старый-добрый SAP.
Но, учитывая политику поддержки, стоимость, непрерывное устаревание, низкую производительность и довольно посредственное отношение к безопасности — я бы не стал вообще рассматривать это решение.
В основновном, хотят что бы:
В приципе SOA в данном случае является почти идеальным решением, и рано или поздно, начиная разработку с какого-то популярного MVC-фреймворка, добавляя различные очереди сообщений и задач, реализуя межпроцессное взаимодействие, разработчики со своих велосипедов скатываются в SOA с CQRS-ES'ом, просто не знают как оно в дикой природе называется.
На практике, получается так что и контроль качества напрочь отсутствует, эволюционного рефакторинга нет и оценка рисков довольно посредственна. Делают «лишь бы работало» и особо понимания что будет через полгода ни у кого нет — проблемы решаются по мере поступления и конечно же это негативно сказывается на самом сервисе и самих разработчиках. В данном случае SOA является лакомой «нямкой» ибо вкусные реюзабильные «чёрные кубики» позволяют забыть о многих шаблонных задачах и сконцентрировать внимание на бизнес-логике приложения, а большая часть менеджмента рисков ложиться непосредственно на сообщество разработчиков «нямки».
Потому что сложно.
Что бы въехать в это всё — недостаточно просто прочитать пару книжек о DDD и о Шаблонах корпоративных приложений, надо это всё реализовать на практике и набить кучу шишек… понять что не всё «правда» что в книжках пишут, и что даже то что «правда» в большинстве существующих OpenSource проектов не реализовано и следовательно не может быть взято за основу собственных.
Ключевой особенностью любого SOA является наличие IPC шины, системы модульности и Discovery сервиса (регистратора). Хорошим примером «как вещи могли бы выглядеть» является Symfony2 и Meteor.js — их модульность позволяет напрямую внедрять новые представления и доменную модель, плагины других фреймворков чаще всего просто расширяют функционал самого фреймворка, добавляют какой-то scaffolding, но непосредственно к реализации и логике работы ваших приложений прямого отношения не имеют. В принципе если сюда добавить шину на каком-нить Gearmand / Beanstalk / RabbitMQ etc то можно даже получить какое-то более-менее SOA. Хотя, имхо, AMQP — дикий оверкилл ибо ноги растут с OASIS'a и избыточность ужаснейшая. Мне очень нравятся подходы protobuf'a, хотя его производительность могла бы быть и получше.
App Data over Avian Carriers with Quality of Service
Я часто видел как люди пытались внедрять XMPP в качестве транспортного протокола для своих мобильных приложений, иногда даже BOSH использовали для вэбсайтов с восклицанием «это ж ejabberd! это ж erlang! это ж хайолдъ!». Человекам не свойственно мерить накладные расходы на коммуникацию и сериализацию-десериализацию объектов, хотя так уж получается что это около 20% всего времени работы приложения под нагрузкой. Даже банальная смена сетевого IP сокета на файловый UNIX сокет может сохранить довольно много процессорного времени из-за отработки IP стэка, а так ведь весь трафик приложения ещё пройдёт через фаэрвол ОСи и прочие непотребства.
Scaling /dev/null
Часто замечал восторженные посты типа "вот, перешли мы с рельсов на Go и сильно сократили серверный парк". Понравилась фраза «And we’ve never had another colossal clusterf**k since». Да, это действительно так: если вы не можете нагрузить ваше оборудование на 100% при обработке запросов — вы масштабируете вакуум. Ситуация с C#/Java на самом деле не сильно отличается. Банальные датамапперы — не более чем bytecode enhancing в котором дополнительно прячется несколько дырявых абстракций в виде многоуровневых кэшей и менеджеров сущностей. Если переписать всё руками получится в разы быстрее, сравнить хотя бы DataNucleus и старый-добрый Hibernate. Почему так происходит — тема отдельной статьи, но если бы были хорошие препроцессоры исходников, типа сишных, — не нужны были бы подобные костыли, и не нужно было копаться в байт-коде, заморачиваясь с совместимостью. Я лично пока остановился на Rust'e — хорошее соотношение безопасности и производительности, и нет такого количества неявных непонятностей как в Go (я об интерфейсах и «duck typing»).
Примеров непотребств и неудачных попыток довольно много, но ползли в правильном направлении, хотя обычно не доползали.
80% проектов с которыми мне приходилось иметь дело не имеют нормальной нормализованной реляционной модели. Первым делом стоит нормализировать вашу модельку аж до 6ой формы, и принять читабельные правила именования табличек и колонок.
Потому что, это
Для сложных и больших запросов стоит использовать материализованные представления — это позволит сэкономить чуть времени профилятору. Прежде чем подумать о денормализации модели — подумайте о хранении промежуточных результатов во временных представлениях.
При нормализованной БД можно оформить такой роутер для ваших CRUD'ов:
В случае со сложенными ключами можно придумать что-то типа $id1_id2_id3 (на ваше усмотрение), и, например, колонки ключа брать в алфавитном порядке. Конечно вам предварительно нужно будет прочитать схему базы, и использовать её для валидации роутера, иначе получите кучу слепых SQL-инъекций.
Ну и тестить, тестить, профилировать, уменьшать время выполнения тестов, рефакторить и опять тестить.
По этому же принципу работает вышеупомянутый TastyPie.
С JS фронтендом я, лично, пока ещё и сам не понимаю что и куда.
С одной стороны мы имеем довольно много всяких стандартов, препроцессоров, постпроцессоров, с другой стороны они совсем не упрощают разработку. Большая часть существующих решений требует дублирования и передачи ваших шаблонов в браузер, после чего вы ещё должны руками организовать EventSourcing, частично дублировать вашу модель в JS и прогнать валидацию… и ничего работающего «с коробки».
Часто, наблюдая за развитием существующих инструментов со стороны, у меня сложилось впечатление что чем дальше мы едем — тем сложнее они становятся, и всё меньше и меньше нацелены на то что бы решать наши практические задачи. При этом все существующие фронтенд проекты собирают довольно большие инвестиции, речь идёт о десятках миллионов у.дмурдских е.жей, и стараются привлечь и заострить к себе внимание.
Я остановился пока на React.js с «изоморфными» рендерами в ноде, хоть меня от этого уже порядком подташнивает. Тут проблема в том, что нужно рендерить одновременно на сервере и на клиенте для нормального UX, в любом случае, а различные webkit суррогаты типа prerender.io не обладают вменяемой производительностью.
Я один из тех, кто не очень то и понимает зачем лепить node.js где не попадя, просто потому что современные JS-фронтенды не особо то и рендерятся в других окружениях. Вот, в принципе, ничего же не мешает перегонять html в json — передавать шаблоны и изменения на страницах по вэбсокетам и longpolling'у и при переходах по ссылкам. После чего применять «патч» к текущему DOM дереву — тогда не нужно будет заморачиваться с роутингом и моделями на страницах, а правила валидации форм можно описать через data-* аттрибуты и регулярные выражения. Сразу же отпадает потребность во всяких VirtualDOM'ах так как все изменения в коллекциях и шаблонах можно отследить уже на стороне сервера и отправлять отрендеренные патчи.
Ну в общем написали вы свои шаблоны на привычных jinja / twig'aх и прочем ctpp, добавили один JS на страницу и специализированный контроллер для менеджмента коллекций, и всё. Правда такая штука будет полноценно работать (с push-нотификациями) только при наличии EventSourcing'a. Таким образом совсем не нужно дублировать модель и представление со стороны браузера, а это довольно большой кусок работы. Надеюсь идея понятна.
Можно передавать HTML страницы с шаблонизатора в виде подобного JSON'a
Мне не известно ни одного существующего инструмента для JS-фронтенда, в составе современных MVC фреймворков, который выполнял бы браузерную шаблонизацию и менеджмент коллекций подобным образом. Хотя менеджмент коллекций и ES уже есть в Meteor.js, но это скорее исключение, и у Яндекса где-то было что-то похожее…
Надеюсь было не слишком скучно, я смог поведать что-то новое, и натолкнуть вас самих на размышления.
Желаю вам приятного времени суток.
По потребности буду тут что-то дописывать, править ошибки и уточнять.
Все замечания направляйте в личку, не засоряйте комментарии.
Сегодня я хотел бы поделиться с вами своим опытом и идеями в сфере проектирования и разработки ПО, развеять некоторые предрассудки и пролить свет на текущее состояние SOA решений в нашем, не совсем "реактивном", мире. О том, как писать меньше кода, получать меньше недовольных клиентов, седины, и больше профита. Реализовывать полностью реюзабельные решения без побочных эффектов, и почему до сих пор этого никто не сделал ⊙.☉
(ง︡'-'︠)ง И так, Приветствую. Спасибо что решили уделить мне
Постараюсь изложить всё кратко и понятно, хотя последнее у меня, обычно, хреново получается.
Предположительное содержание
0 Введение 0.1 Что такое Сервис Ориентированная Архитектура ? 0.2 Какие существуют преимущества и недостатки существующих решений ? 0.3 Каковы требования современного рынка ? 0.4 Почему этого ещё никто не сделал ? 1 Сервис 1.1 Домены и реляционная модель 1.2 Решение задачи консенсуса 1.3 Масштабирование и шардинг 1.4 Отказоустойчивость 1.5 Логирование и мониторинг 1.6 Поиск и регистрация 1.7 AAA 2 Представление 2.1 Гипермедиа типы и scaffolding 2.2 CQRS-ES all the things 2.3 Шаблонизация 2.4 Протоколы и шлюзы 2.5 Счётчики, отчёты и статистика 3. Поддержка 3.1 Документирование 3.2 Обратная совместимость 3.3 Миграции 3.4 Корректность 3.5 Жизненный цикл решения
Введение
Пару лет назад я столкнулся с проблемой нехватки инструментов для реализации бэкенд и фронтенд решений (вэб, десктоп, мобильное). С того времени много жидкости утекло, я ознакомился с большим количеством существующих подходов и у меня возникли идеи как всё сделать чуть лучше чем оно есть сейчас. Изучив текущее положение на рынке, я понял что существующие решения специально не дают полного контроля над приложениями и являются либо пустышками, которые сами по себе лишь маленькая часть всей архитектуры проектов; их целью является поддержание интереса клиентов и экспертной аудитории, что позитивно сказывается на стоимости конторы, либо наркотиком поддержки — многослойный ужас с различными мутантами спецификаций OASIS'a, где на поддержку и тренинг персонала нужно нехило отлистать. Наиболее ярким примером последнего является стэк WSO2. Я считаю его наиболее полным SOA решением на рынке, хоть и монструозным, кривым и очень избыточным. Подобные вещи сложно сопоставить Agile методологии, так как они все крутятся вокруг бумаг и бизнес-процессов, а не вокруг потребностей живых людей (первый пункт Agile манифеста). В этих статьях я хочу поделиться своим видением проектирования SOA, как его адаптировать для требований современных рынков и RAD'a, описать основные преимущества подобных подходов.
Сейчас очень модно обсуждать различные реактивные веяния — что системы должны быть:
- Отзывчивыми
- Отказоустойчивыми
- Масштабируемыми
- Управляемыми потоками событий (сообщений)
Картинка
На практике получается так что всё это сплошная маркетинговая муть для привлечения внимания инвесторов, и, в общем то, большая часть решений не представляет какой либо практической ценности, так как всё эту «реактивность» разработчики должны реализовывать своими силами, без каких либо готовых решений или наработок по существующим требованиям. Даже полноценного ААА нигде нет.
Другое дело SOA — там это всё уже должно работать прям с коробки, но как бы не так.
Вот получается так, что у каждого вендора своё SOA с шахматами и поэтессами, и друг с другом они не дружат, хотя с OpenSource решениями всё хорошо, об этом далее. Это всё достаточно быстро устаревает, как-то совсем не формируются новые сообщества, и проекты разрабатываются лишь усилиями их владельцев. Если это SOA, и любой сервис можно использовать хоть 100500 раз в целой пачке проектов, то почему у нас до сих пор нет стандартизированного репозитория реюзабельных сервисов и доменов? В этом просто не заинтересованы владельцы существующих решений — ведь нужно же денег с поддержки высасывать.
Особенно весело обсуждают последний год микросервисы. Я считаю что это довольно таки дурацкое название, и совсем они не «микро», так как подразумевают глубокую инкапсуляцию нескольких протоколов. По этому выполнять «микросервисы» в рамках одного сервера, или виртуальных машин, — лишь увеличивать задержки да вакуум масштабировать. А вообще, в классическом SOA совсем без разницы является ли сервис частью единого системного процесса, или он должен быть запущен в виде отдельного наборов процессов со своими очередями и протоколами на разных машинах — всё это лишь вопрос регистрации сервиса и его зависимостей. Так что я считаю «микросервисы», тоже, не более чем маркетинговой уловкой — попыткой «переосмысления» очевидных старых идей, для того что бы выудить побольше денег.
«Ok, а что не так с MVC ?» — наверняка, задумаетесь вы… а ничего, оно есть, оно для простых графических интерфейсов, без наворотов, и в неумелых руках даже банальный CRUD — это тонна копи-пасты. MVC плохо контролирует предметную область, поощряет хранение состояния и дублирование кода, в нём плохо разделены обязанности логики (сегрегации команд и запросов), поощряется денормализация модели. Другое дело велосипеды: ADR, всякие ECB, EBI с Clean Architecture и прочие вариации на тему Hexagonal Architecture — они являются попыткой переработать MVC, конечно не без своих индивидуальных недостатков и, естественно, не у всех хватит опыта и навыков для реализации подобных вещей, но в них решено довольно много проблем присущих MVC. Всё это создано, по большей части, за последние 3 года PHP-адептами и соответсвенно, так или иначе, отражаются особенности этой платформы. Не то что бы я очень нелюбил РНР, но современные вариации, в куче с Python'ом и Ruby, не позволяют полностью удовлетворить требования современных рынков.
Я понимаю что могу задеть чувства адептов культа "Золотого молота", но далее будет понятно почему всё так совсем не оптимистично, и покажу что придумал на этот счёт.
А есть ещё Flux, но это тоже ещё одна, очередная, маркетинговая уловка.
Flux в простонародье — ни что иное как более сложная аббревиатура CQRS-ES, потому Facebook и хотели упростить — что бы можно было привлечь внимание и плодить
Именно CQRS-ES паттерн
Главной проблемой современного SOA, да и вообще всех существующих архитектур, является довольно плохое абстрагирование предметной области и определение доменной модели. Я рассмотрю этот вопрос с точки зрения синхронизированных дискретных конечных автоматов FSM и покажу как можно более корректно (без избыточности) определять домены, учитывая современные требования к приложениям.
Как говорил Эванс:
Сложности не там где все эти фреймворки, базы данных, очереди, и прочие вещи от которых так тащатся инженеры, сложности всегда прятались в описании и моделировании предметной области — создании доменов.Не то что бы я хотел так резко перевернуть ваши представления об архитектуре приложений, но это необходимо ввиду того что ~80% состоят, чуть более чем полностью, с костылей, с полированной слоновой кости, инкрустированных бриллиантами, потому что бюджет позволяет и «почему бы и нет: нам же денег платят, и оно работает — зачем учить что-то новое ?».
Почему я так к этому отношусь
Killjoys S01E06
Killjoys S01E06
«Ок, мне надоело читать, в чём заключается твоя идея ?»
В конце жизненный цикл разработки бэкенда и фронтенда должен выглядеть вот так:
Остаётся лишь дизайн (по желанию) и реализация недостающего функционала, посредством добавления своих сервисов и доменов в систему.
Это как будто если бы у вас был бы свой карманный Firebase, который кроме того что автоматом вносил бы все правки и миграции в базу, ещё бы и производил Scaffolding для ваших мобильных, десктоп и вэб приложений: автоматически генерировал все представления, шаблоны, валидацию и клиенты к API c Push нотификациями, и асинхронным UI.
Из-за того что большая часть любого современного функционала очень шаблонна — нет смысла от проекта к проекту переписывать всё каждый раз «с нуля». Нужна платформа для того что бы люди могли поделится своими наработками и стандартизировать контроль качества и взаимодействие между ними. Таким образом можно получить сообщество пользователей которое неявно мотивируется к повышению качества продукции и квалификации его членов, добавим сюда геймификацию с повышением уровней, ачивками и шуточными статами… ну вы поняли — фантазии есть где разгуляться.
При разработке подобного решения нужно предотвратить возникновение двух проблемных ситуаций — это «разработка комитетом» и «разработка по спецификациям», что собственно привело к ужасающему раздуванию J2EE стека.
Сейчас почти никто не заинтересован в автоматизации труда разработчиков, хотя большая часть кода существующих приложений может быть сгенерирована автоматически или абстрагирована средствами динамического программирования.
Да… если говорить очень грубо и упрощённо: по схеме БД можно сразу сгенерировать REST эндпоинты, клиент API и шаблоны для вашего фронта на любимом Angular'e / React'e / Ember'e и т.п. Хоть я и не очень перевариваю подходы современных браузерных фреймворков, об этом далее. Остаётся прописать группы пользователей и их права, натянуть существующий дизайн. Если вы считаете подобное мистикой — ну вот Tastypie уже умеет генерировать REST эндпоинты с модельки, правда там не хватает фич и производительность довольно посредственная, даже для Python'a, из-за плохого дизайнa, но в качестве примера его использовать можно.
Что такое Сервис Ориентированная Архитектура ?
Это идея. Она состоит в том что приложения должны состоять из реюзабельных взаимозаменяемых «чёрных коробочек» — сервисов, которые общаются по стандартизированным протоколам и интрефесам между собой.
Есть несколько важных условий, сервисы:
- Должны Выполняться в любых условиях и масштабироваться по требованию
- Должны Быть ортогональными (слабосвязанными) и отказоустойчивыми
- Должны Иметь возможность повторного использования
- Не должны Хранить состояний
- Должны Быть максимально абстрагированы
- Должны Иметь возможность использования в нескольких бизнес-процессах одновременно, без побочных эффектов
- Должны Иметь возможность динамической регистрации и внедрения «на лету»
В принципе, по первым трём условиям должно быть более-менее понятно. Сервисы не зависят друг от друга, не разделяют общих ресурсов, и могут мигрировать между процессами как локальной так и удалённых машин. Могут быть повторно и одновременно использованы в нескольких проектах.
По поводу состояний — всё просто: сервер не должен хранить данных о пользовательской сессии, это делается для того что бы запрос мог быть обработан любым экземпляром запущенного сервиса, обычно наименее загруженным в данный момент времени. Это делается посредством внедрения шифрованной, «просоленной» информации о сессии в пользовательский запрос. Для того что бы не передавать эту полезную нагрузку при каждом запросе — обычно создают отдельный сервис кратковременной регистрации пользовательских сессий, который временно их кэширует. Создание глобального сервиса для хранения пользовательских сессий приведёт к появлению «бутылочного горлышка» в котором ещё нужно будет решить задачу консенсуса что
С абстракциями всё довольно туго — те или иные домены (схема базы и логика), должны быть динамически расширяемы в зависимости от конкретных потребностей зависимых сервисов и доменов. Допустим, существует сервис учёта (Accounting) и в нём зарегистрированы различные услуги — для некоторых в тарификации предусмотрена только постоплата, другие же требуют предоплаты. Добавим сюда ещё различных скидочных и накопительных систем и получаем качественную головную боль на многие месяцы, потому что вся логика работы этих компонент постоянно меняется. Большую роль здесь играет нормализация реляционной модели — прошу заметить что нормальных форм уже давно шесть. Хорошее абстрагирование и детализация предметной области позволяет уменьшить сложность внедрения новой, или изменения существующей, логики, но об этом в следующей части.
Возможность разделения доступа к сервисам между несколькими «подчинёнными» и «руководителями» следует из требования к слабой связности и отсутствии разделяемых состояний. Всё прям как с многопоточным программированием и методами предотвращения состояний «гонки». Отдельно стоит упомянуть случай расширения доменов: там существует проблема совместимости при совместном использовании расширенного домена несколькими сервисами. В общем, грубо-говоря, когда одному сервису нужна табличка с одним количеством полей и состояний (ну там, значение перечисления), а другому — с совсем другим, и как их «подружить» что бы не было проблем при взаимном использовании этой таблички, или какого-то другого ресурса.
Есть много системных «корневых» сервисов которые отвечают за работу всех остальных, и за их поведение в случае возникновения ошибок. Одним из них является Регистратор который позволяет регистрировать
Думаю, на этой ноте можно завершить обзор задач и требований SOA.
По потребности что-то сюда ещё добавлю.
Вот диаграмма как я вообще представляю структуру сервис-ориентированной архитектуры.
Давайте рассмотрим предназначение корневых сервисов.
- Регистратор — как уже было сказано выше, занимается регистрацией и запуском / остановкой сервисов. Также он хранит адреса и описание протоколов для подключения, проверяет корректность работы сервисов после обновлений и может выполнить откат на предыдущую работоспособную версию автоматически, без приостановки работы.
- Менеджер ресурсов — один из наиболее важных сервисов, он координирует использование существующих ресурсов: выделения физических ресурсов для работы сервисов, производит их мониторинг для распределения нагрузки. Можно реализовать на основе Provisioning и DevOps решений типа OpenStack. Менеджеры ресурсов часто ещё называют SOI.
- Архивариус — сервис для логирования событий и сбора статистики, подготовки отчётов.
- Курьер — занимается доставкой и маршрутизацией сообщений.
- Надзиратель — следит за работоспособностью всех существующих сервисов, производит перезапуск по требованию и блокирование недоброжелательных пользователей.
- ААА — это 3 сервиса отвечающие за
- Аутентификацию (Authentication) — подтверждение личности пользователя.
Это логины/пароли, биометрика, OAuth, рассылка SMS'ок для входа и т.п. - Авторизацию (Authorization) — разделение прав пользователей.
Обычно это различные именованные группы, которым доступны различные действия - Учёт (Accounting) — купля/продажа различных услуг посредством платёжных сервисов, история поточных счетов, скидочные и накопительные системы. Часто реализуются методы для учёта различных товаров в местах хранения, их бронирование и доступность.
- Аутентификацию (Authentication) — подтверждение личности пользователя.
Каждый из этих сервисов имеет свои особенности дизайна (программного) и я постараюсь описать все детали в следующих статьях, надеюсь ничего не упущу.
Любой проект должен содержать, как минимум, Регистратор, Архивариус, Менеджер Ресурсов и ААА, хотя вы, наверняка, можете придумать названия и получше. Два последних упускают по ряду причин связанных со сложностью реализации расширяемых доменов (привет кокаин).
Какие преимущества и недостатки существующих решений ?
Полноценного SOA сейчас нет — везде где-то чего-то не хватает.
О кодогенерации по гипермедиатипам история вообще умалчивает.
Я работал с
Конечно в случае с J2EE стеком наблюдается ужасающая избыточность и для высоких нагрузок ни WSO ни JBoss не годятся. Да и в общем «слабое связывание» не шибко вяжется к современным слоёным j2ee EJB плюшкам.
Cocaine и Bluemix я, лично, отношу к SOA решениям лишь частично — нет готовой системы внедрения и стандартизации взаимодействия существующих серверов, полный DIY. В случае с кокаином — есть locator, хоть и довольно рудиментарный, но хорошо что хоть что-то. В bluemix можно реализовать неплохое SOI, но тоже нужно поиграться чуток. С точки зрения SOI наиболее гибким решением являются сервисы Amazon.
Все вендоры предлагают разворачивать инфраструктуру только на своих PaaS'ах, особенно это заметно в случае с Oracle и её Integration Cloud Service. Нужно понимать что большинству пользователей SOA решений нужно что бы все сервера находились в пределах одного датацентра, а задержки при коммуникации были минимальны — часто приходится своими силами организовывать CDN'ы и прочее распределённое барахло, а лишний vendor lockin при выборе датацентра лишь связывает руки. Уже и Amazon и Azure отваливались — серьёзным и ответственным клиентам лучше производить менеджмент рисков самостоятельно, а для простых обывателей хватит и «запустили, работает — пошли
BPEL/BPMN — отдельная тема: попытались написать язык описания бизнес процессов для секретарш, а получили многоликое непонятно что — то что не предусматривается стандартом реализовать без правок самой спецификации языка почти невозможно. Для этого нужно связываться с OASIS'ом, вступать в консорциум, выпускать RFC… ну вы поняли — ничего общего непосредственно с решением поставленных задач, а лишь плата за существования многоликих монолитных стандартов, которые невозможно адаптировать для современных требований рынка в приемлемых временных рамках. Проще, на много проще, брать и писать всё руками… на любом любимом языке. Описание бизнес-процессов можно очень упростить, если рассматривать его с точки зрения конечных автоматов (FSM).
Главным преимуществом существующих решений является гарантии со стороны вендоров, которых достаточно для использования в мед. секторе и частично в забугорном гос. и automotive секторе — там нет таких жёстких требований рынка и там не нужно следовать «последним веяниям моды». Следовательно для реализации новых проектов, или даже, не побоюсь этого слова, стартапов — эти решения и платформы не годятся.
Также, как полноценное SOA можно рассматривать старый-добрый SAP.
Но, учитывая политику поддержки, стоимость, непрерывное устаревание, низкую производительность и довольно посредственное отношение к безопасности — я бы не стал вообще рассматривать это решение.
Каковы требования современного рынка ?
В основновном, хотят что бы:
- Приложение могло выдержать очень большие скачки нагрузки
- Контент обновлялся в реальном времени и употреблялся на разнообразных устройствах
- Не нужно было заморачиваться с долгосрочной поддержкой
- Не нужно было часто закупать новое оборудование
- BusFactor был больше 30%
В приципе SOA в данном случае является почти идеальным решением, и рано или поздно, начиная разработку с какого-то популярного MVC-фреймворка, добавляя различные очереди сообщений и задач, реализуя межпроцессное взаимодействие, разработчики со своих велосипедов скатываются в SOA с CQRS-ES'ом, просто не знают как оно в дикой природе называется.
На практике, получается так что и контроль качества напрочь отсутствует, эволюционного рефакторинга нет и оценка рисков довольно посредственна. Делают «лишь бы работало» и особо понимания что будет через полгода ни у кого нет — проблемы решаются по мере поступления и конечно же это негативно сказывается на самом сервисе и самих разработчиках. В данном случае SOA является лакомой «нямкой» ибо вкусные реюзабильные «чёрные кубики» позволяют забыть о многих шаблонных задачах и сконцентрировать внимание на бизнес-логике приложения, а большая часть менеджмента рисков ложиться непосредственно на сообщество разработчиков «нямки».
Почему этого ещё никто не сделал ?
Потому что сложно.
Что бы въехать в это всё — недостаточно просто прочитать пару книжек о DDD и о Шаблонах корпоративных приложений, надо это всё реализовать на практике и набить кучу шишек… понять что не всё «правда» что в книжках пишут, и что даже то что «правда» в большинстве существующих OpenSource проектов не реализовано и следовательно не может быть взято за основу собственных.
Ключевой особенностью любого SOA является наличие IPC шины, системы модульности и Discovery сервиса (регистратора). Хорошим примером «как вещи могли бы выглядеть» является Symfony2 и Meteor.js — их модульность позволяет напрямую внедрять новые представления и доменную модель, плагины других фреймворков чаще всего просто расширяют функционал самого фреймворка, добавляют какой-то scaffolding, но непосредственно к реализации и логике работы ваших приложений прямого отношения не имеют. В принципе если сюда добавить шину на каком-нить Gearmand / Beanstalk / RabbitMQ etc то можно даже получить какое-то более-менее SOA. Хотя, имхо, AMQP — дикий оверкилл ибо ноги растут с OASIS'a и избыточность ужаснейшая. Мне очень нравятся подходы protobuf'a, хотя его производительность могла бы быть и получше.
App Data over Avian Carriers with Quality of Service
Я часто видел как люди пытались внедрять XMPP в качестве транспортного протокола для своих мобильных приложений, иногда даже BOSH использовали для вэбсайтов с восклицанием «это ж ejabberd! это ж erlang! это ж хайолдъ!». Человекам не свойственно мерить накладные расходы на коммуникацию и сериализацию-десериализацию объектов, хотя так уж получается что это около 20% всего времени работы приложения под нагрузкой. Даже банальная смена сетевого IP сокета на файловый UNIX сокет может сохранить довольно много процессорного времени из-за отработки IP стэка, а так ведь весь трафик приложения ещё пройдёт через фаэрвол ОСи и прочие непотребства.
Scaling /dev/null
Часто замечал восторженные посты типа "вот, перешли мы с рельсов на Go и сильно сократили серверный парк". Понравилась фраза «And we’ve never had another colossal clusterf**k since». Да, это действительно так: если вы не можете нагрузить ваше оборудование на 100% при обработке запросов — вы масштабируете вакуум. Ситуация с C#/Java на самом деле не сильно отличается. Банальные датамапперы — не более чем bytecode enhancing в котором дополнительно прячется несколько дырявых абстракций в виде многоуровневых кэшей и менеджеров сущностей. Если переписать всё руками получится в разы быстрее, сравнить хотя бы DataNucleus и старый-добрый Hibernate. Почему так происходит — тема отдельной статьи, но если бы были хорошие препроцессоры исходников, типа сишных, — не нужны были бы подобные костыли, и не нужно было копаться в байт-коде, заморачиваясь с совместимостью. Я лично пока остановился на Rust'e — хорошее соотношение безопасности и производительности, и нет такого количества неявных непонятностей как в Go (я об интерфейсах и «duck typing»).
Примеров непотребств и неудачных попыток довольно много, но ползли в правильном направлении, хотя обычно не доползали.
Ок, что нам делать с нашим MV-что-то-там ?
80% проектов с которыми мне приходилось иметь дело не имеют нормальной нормализованной реляционной модели. Первым делом стоит нормализировать вашу модельку аж до 6ой формы, и принять читабельные правила именования табличек и колонок.
Потому что, это
- Позволит ускорить большую часть запросов
- Предотвратит избыточность и уменьшит размеры вашей базы
- Упростит поддержку и документирование вашего решения
- Упростит реализацию существующей CRUD логики
Для сложных и больших запросов стоит использовать материализованные представления — это позволит сэкономить чуть времени профилятору. Прежде чем подумать о денормализации модели — подумайте о хранении промежуточных результатов во временных представлениях.
При нормализованной БД можно оформить такой роутер для ваших CRUD'ов:
GET /plural($tableName) // получить все записи таблицы
GET /plural($tableName)/page/$pageNumber // получить записи на странице
GET /plural($tableName)/page/$pageNumber/size/$pageSize // получить записи на странице заданного размера
GET /plural($tableName)/where/$fieldName/$value // найти все записи у которых поле имеет значение
GET /$tableName/$id // получить запись с $id
GET /$tableName/$id/plural($referencedTableName) // получить все записи с дочерней таблицы с родительским $id
POST /$tableName/$id // изменить запись с ID
PUT /$tableName // создать новую запись
PUT /$tableName/$id/$referencedTableName/$referenced_id // добавить указанную дочернюю запись в ManyToMany таблицу
DELETE /$tableName/$id // удалить запись с $id
DELETE /$tableName/$id/$referencedTableName/$referenced_id // удалить указанную дочернюю запись с ManyToMany таблицы
В случае со сложенными ключами можно придумать что-то типа $id1_id2_id3 (на ваше усмотрение), и, например, колонки ключа брать в алфавитном порядке. Конечно вам предварительно нужно будет прочитать схему базы, и использовать её для валидации роутера, иначе получите кучу слепых SQL-инъекций.
Ну и тестить, тестить, профилировать, уменьшать время выполнения тестов, рефакторить и опять тестить.
По этому же принципу работает вышеупомянутый TastyPie.
Ok, а что с JS-фронтендом ?
С JS фронтендом я, лично, пока ещё и сам не понимаю что и куда.
С одной стороны мы имеем довольно много всяких стандартов, препроцессоров, постпроцессоров, с другой стороны они совсем не упрощают разработку. Большая часть существующих решений требует дублирования и передачи ваших шаблонов в браузер, после чего вы ещё должны руками организовать EventSourcing, частично дублировать вашу модель в JS и прогнать валидацию… и ничего работающего «с коробки».
Часто, наблюдая за развитием существующих инструментов со стороны, у меня сложилось впечатление что чем дальше мы едем — тем сложнее они становятся, и всё меньше и меньше нацелены на то что бы решать наши практические задачи. При этом все существующие фронтенд проекты собирают довольно большие инвестиции, речь идёт о десятках миллионов у.дмурдских е.жей, и стараются привлечь и заострить к себе внимание.
Я остановился пока на React.js с «изоморфными» рендерами в ноде, хоть меня от этого уже порядком подташнивает. Тут проблема в том, что нужно рендерить одновременно на сервере и на клиенте для нормального UX, в любом случае, а различные webkit суррогаты типа prerender.io не обладают вменяемой производительностью.
Я один из тех, кто не очень то и понимает зачем лепить node.js где не попадя, просто потому что современные JS-фронтенды не особо то и рендерятся в других окружениях. Вот, в принципе, ничего же не мешает перегонять html в json — передавать шаблоны и изменения на страницах по вэбсокетам и longpolling'у и при переходах по ссылкам. После чего применять «патч» к текущему DOM дереву — тогда не нужно будет заморачиваться с роутингом и моделями на страницах, а правила валидации форм можно описать через data-* аттрибуты и регулярные выражения. Сразу же отпадает потребность во всяких VirtualDOM'ах так как все изменения в коллекциях и шаблонах можно отследить уже на стороне сервера и отправлять отрендеренные патчи.
Ну в общем написали вы свои шаблоны на привычных jinja / twig'aх и прочем ctpp, добавили один JS на страницу и специализированный контроллер для менеджмента коллекций, и всё. Правда такая штука будет полноценно работать (с push-нотификациями) только при наличии EventSourcing'a. Таким образом совсем не нужно дублировать модель и представление со стороны браузера, а это довольно большой кусок работы. Надеюсь идея понятна.
Можно передавать HTML страницы с шаблонизатора в виде подобного JSON'a
{
"action": "append|remove|replace",
"selector": "#HaveANiceDay",
"tagName": { // optional
"attrs": [
{ "name": "value" },
"name",
],
"content": {
{"otherTagName": { ... }},
},
},
}
Мне не известно ни одного существующего инструмента для JS-фронтенда, в составе современных MVC фреймворков, который выполнял бы браузерную шаблонизацию и менеджмент коллекций подобным образом. Хотя менеджмент коллекций и ES уже есть в Meteor.js, но это скорее исключение, и у Яндекса где-то было что-то похожее…
Ок. Вполне так более чем достаточно для введения.
Надеюсь было не слишком скучно, я смог поведать что-то новое, и натолкнуть вас самих на размышления.
Желаю вам приятного времени суток.
По потребности буду тут что-то дописывать, править ошибки и уточнять.
Все замечания направляйте в личку, не засоряйте комментарии.