Как стать автором
Обновить

Комментарии 41

"… Следующий рисунок отображает наилучший способ"
Простите, это у вас изображено клонирование? )
типа того, тоже думал чего автор имел в виду
думаю, что надо так скакать между слоями/модулями, чтобы между ними никакой паразитной связи построить было нельзя
ой, не заметил что это перевод, извиняюсь)
А вообще неплохо mvc разжевано.
Да MVC-то тут как раз ни при чем. Тут все ближе к DDD и про подход «Design-First».
Для DDD тут слишком мало D(omain). Я уже писал в комментах к первой части, что здесь средний слой — не бизнес-логика, а дата-маппер.

А вся остальная вода — про то, как разделить в головах людей работу между слоями, а само содержимое слоя значения не имеет.
Возможно. Но про DDD тут «больше», чем про MVC — ибо для MVC тут слишком мало ©ontroller'а.
Давайте скажем так: тут про SoC.

Что DDD, что MVC тут одинаково мало.
Верно, согласен.
Хм, я просто MVC не так видимо понимаю. Я представляю это как некий концепт разделения уровней приложения. А вот про SoC я кажется не знаю, или не знаю что оно так называется ) Можно ссылочку или хотя бы расшифровку аббревиатуры?
SoC — a.k.a. Separation of Concerns — это весьма и весьма общая абстрактная вещь, принцип разделения функциональности и ответственности программных элементов в целом.

MVC — это паттерн, позволяющий «реализовать» SoC в достаточно конкретном контексте (например, веб-приложении, если речь о web-MVC а ля RoR, Django, ASP.NET MVC, и т.д.)

P.S. В контексте SoC для построения хорошей архитектуры очень важно знать и правильно понимать принципы SOLID.
SOLID это хорошо, согласен )
За SoC спасибо.
Может быть имеется в виду параллельная работа над всеми частями?
Имхо, статье не хватает другого взгляда. Сейчас все больше программы превращаются в хранение информации + ее представление практически без бизнес слоя. А когда бизнес слой, фактически, является только лишь промежуточным звеном от GUI к базе, зависимость представления от слоя хранения и наоборот неизбежно будет высокой. Привет паттерну MVP…

Безусловно, данный текст актуален для большого ряда программ, но его, имхо, не стоит воспринимать как 100%-ное руководство к действию и жертвовать простотой ради введения еще одного уровня абстракции
Никакой текст не стоит воспринимать как руководство к действию. Разве что «Устав караульной службы», да и за его нарушения бывало медали давали.

Подход описанный в статье имеет смысл, когда:
а) нужно максимально распараллелить задачи;
б) разрабатываемое приложение может потребовать резкого масштабирования;
в) создается заведомо высоконагруженные приложение, проще находить и обходить узкие места.
Те пункты, что вы описали, никакого отношения к статье не имеют. Легкость тут обеспечивается нормальной модульностью. И в MVC можно такого наворотить…
наворотить можно на чём угодно и даже без компьютера, также как и сделать хорошо
на ваш взгляд, когда такой подход уместен? если описанные мною пункты не подходят
А при чем тут масштабирование вообще? В оригинальной статье про него ни слова не сказано.
мне уже интересно стало
как вы понимаете слово «масштабирование»?
Так же, как и авторы большей части современных книг по архитектуре.

Масштабирование (scalability) — это увеличение производительности системы за счет добавления оборудования. Масштабирование бывает вертикальное (увеличение мощности конкретного вычислительного звена) и горизонтальное (добавление звеньев для параллельной обработки).
> Если кто-то подскажет как с этой напастью справиться: буду благодарен.
--Сохранить текст в виде большой картинки и положить в конец статьи (шутк).
точно, в следующий раз скриншотов напихаю
вместе с заголовками окон и системным треем :)
Спасибо. Вы мне помогли. Сейчас как раз занят рефакторингом.
Хочется отметить одну вещь, которая упущена в статье: разработка проекта ведется не монотонно, а итерациями. Поэтому все эти картинки с шаблона проектирования актуальны только для одной итерации. Видимо после окончания итерации, при наличии уже какого-то рабочего кода, видение системы будет переосмыслено, поэтому знания о слоях необходимо распространить между собой, что бы внести правильные коррективы в следующей итерации. Так что нарушения изоляции слоев видимо не избежать, можно лишь минимизировать.
Описанный в этих статьях подход является не таким уж значительным улучшением обычной трёхзвенки. Да, какие-то проекты этот подход спасёт, им хватит и этого. Особенно, если раньше в этих проектах творился полный ужоснах вроде связей всех со всеми.

Но для больших и сложных проектов этот подход вообще не является удачным выбором! Ему присущи некоторые принципиальные недостатки, некоторые из которых описаны в предыдущих комментариях. А самый главный недостаток: общая сложность проекта при этом подходе практически не уменьшается. Более того, раздирание некоторых специфических фич на части ради соответствия MVC/MVP конкретно добавляет сложности и периодически вызывает к жизни жуткие хаки что «впихнуть невпихуемое» (собственно, те самые связи всех со всеми обычно возникают либо при реализации таких фич, либо когда обычную фичу делать по MVC было лениво).

Вместо этого есть альтернативные подходы к разработке больших проектов, используемые в куче самых разнообразных мест — от веб-сайта и веб-сервисов Амазона до OS Inferno. Вкратце, большой проект разбивается на множество мелких, изолированных, и достаточно простых проектиков (иногда даже совсем тривиальных — вроде проекта единственная фича которого это генерация уникального ID по запросу). Между собой эти проекты общаются обычно по сети, даже если все запущены на одном сервере (это делает переезд выросшего проекта с одного сервера на кластер практически тривиальной и чисто админской задачей). Один из ключевых моментов — чтобы проекты оставались изолированными и слабосвязанными требуется разработать максимально простые интерфейсы между ними. Поскольку проекты небольшие, и в каждом своя личная n-звенка, MVC, бд, блэкджек и шлюхи (кому что нужно), и изолированные — никого не волнует что там внутри, пока внешний интерфейс этого проекта работает корректно. И нет необходимости в рамках одного такого проектика делать кучу ролей и писать его нескольким разработчикам (ну, кроме парного программирования и т.п. вещей не относящихся к описанному в статье). Ещё у этого подхода есть отдельные бонусы вроде возможности писать отдельные проекты под разные ОС и на разных языках программирования — подбирая максимально эффективный язык для каждой задачи или просто загрузив работой всех доступных разработчиков вне зависимости от языка на котором они пишут. Или, к примеру, возможность легко отлаживать проект в целом просто подключаясь к сетевым интерфейсам разных проектов и изучая какие данные там сейчас бегают. Но это всё мелочи. Главное то, что можно спокойно писать отдельные части проекта не держа его весь в голове, и при этом получать качественный результат, который достаточно прост чтобы его было легко поддерживать.
У этого альтернативного подхода хорошо все, кроме одной вещи — если мы заведомо проектируем каждый интерфейс как подходящий для (а) сетевого общения, т.е., remote facade и (б) кроссплатформенный, мы лишаем себя очень как производительности, так и просто возможностей платформы.

Это, конечно, очень круто и модульно, но не везде приемлимо.
Ну для всех понятие производительности разное. Я вот тут общался с одним товарищем, у которого в проектах 10 000 запросов в секунду — детский лепет а не производительность. Но в пределах нескольких килорпс накладные расходы на межсетевое взаимодействие практически незаметны.

Что качается SOA, то я как раз сейчас участвую в проекте, который работает по такому принципу. Какие есть трудности такого подхода:

* На одной машине оно не взлетит, да и вообще глупо. В итоге довольно сложная инфраструктура.
* Собрать проект и запустить у себя локально могут пара человек в комманде, да и то пройдя огромный путь по конфигурированию всех сервисов
* Лично мое субъективное впечатление: сервис-ориентировання архитектура усложняет рефакторинг и как следствие делает систему менее гибкой. Выделять сервис на каждый чих — плохо. Это все равно, что на несколько лет вперед придумать каким-то чудом все интерфейсы, а потом несколько лет писать их реализацию. Я утрирую, конечно, но идея такая.
* Требуются специалисты более высокого класса. К примеру если что-то тупит, но не понятно что, то нужно реально сидеть и анализировать тучу логов — просто вязять и профилировать, как в случае с монолитным приложением, нельзя.

Какие плюсы такого подхода:
* Четкое разделение обязанностей в комманде.
* Получается довольно масштабируемая система
* По скольку сервисы слабо свзяаны, можно добиться, что бы при падении какого-либо сервиса, у пользователей оставалась работоспособной остальная функциональность. Т.е. выше живучесть системы.
* Чисто субъективно: достаточно легко находить баги в системе, если предусмотрен отладочный режим и можно посмотреть все межсервисные вызовы.
«Но в пределах нескольких килорпс накладные расходы на межсетевое взаимодействие практически незаметны.»
По сравнению с локальным вызовом?

«Собрать проект и запустить у себя локально могут пара человек в комманде, да и то пройдя огромный путь по конфигурированию всех сервисов»
Ну, это всего лишь означает, что у вас не построен деливери-лайн. Это не очень хорошо само по себе, но к SOA отношения не имеет.

«Получается довольно масштабируемая система»
А вот это — иллюзия. Сервисы масштабируемы только в том случае, если каждый из них написан масштабируемо. Для «слоеной» архитектуры это верно ровно в той же степени.
>По сравнению с локальным вызовом
И с локальным вызовом и вызовом внутри одного процесса. Насколько я помню, сетка у нас сжирает около 50 мл сек. Учитывая, что каждый сервис обслуживается параллельно десятками ядер, то рпс остается приличным.

>Ну, это всего лишь означает, что у вас не построен деливери-лайн.

Да, возможно. Не расскажете, что такое деливери-лайн (гугл ничего внятного не выдает)? Пока у нас сделано так: сервисы выкатываются deb-пакетами, каждый пакет ставит пример конфига (обычно по дефолту все настроено на девелоперское окружение), далее у каждого сервиса есть документация, по которой и вносятся всякие правки.

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

Не масштабируемые сервисы у нас не пишут, и не писали никогда :) Но все же мне кажется, что получается более гибкая схема, можно более рационально распределять ресурсы.
«И с локальным вызовом и вызовом внутри одного процесса. Насколько я помню, сетка у нас сжирает около 50 мл сек. Учитывая, что каждый сервис обслуживается параллельно десятками ядер, то рпс остается приличным.»
Я никак не могу найти, в какой книжке я видел замечательную табличку про соотношение локального вызова к межпроцесному, и оттуда — к сетевому. Там было очень хорошо видно, чего стоит вынос вызова из локального контекста в удаленный.

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

«Не расскажете, что такое деливери-лайн (гугл ничего внятного не выдает)?»
continuousdelivery.com/

«Пока у нас сделано так: сервисы выкатываются deb-пакетами, каждый пакет ставит пример конфига (обычно по дефолту все настроено на девелоперское окружение), далее у каждого сервиса есть документация, по которой и вносятся всякие правки.»
Ну, с точки зрения CD это классический антипаттерн. Идея CD в том, что все необходимое окружение должно выкатываться максимально автоматически. В идеале — «нажал кнопку, получил среду».

«Не масштабируемые сервисы у нас не пишут, и не писали никогда»
Вот если применить тот же подход к слоям, может оказаться, что слои масштабируются лучше (в данном случае я противопоставляю сервисы слоям в контексте противопоставления традиционной однонаправленной (вертикальной) архитектуры слоев шестигранной архитектуре в SOA).

«Но все же мне кажется, что получается более гибкая схема, можно более рационально распределять ресурсы. „
Это очень сильно зависит от ситуации. Вот у вас выше есть пример, когда в отдельный проект (сервис) вынесена генерация уникального id по запросу. Как мы все понимаем, сама по себе эта задача не очень требовательна к ресурсам, и, скорее всего, высокопроизводительна. Очень может быть, что в ее случае накладные расходы на ее функционирование в качестве сервиса (с учетом поддержания кроссплатформенности и возможности легко вынести в другое звено) превышают расходы на собственно ее работу. Это, как можно догадаться, не есть рациональное распределение ресурсов.

Все, конечно, зависит от того, насколько хитрожопым был разработчик, и как хитро он сделал интеграцию с сервисами. Если он очень хитер, то систему можно использовать in-process/out-of-process/remote-process без изменений клиентского кода. Тогда единственное, что остается в недостатках такой системы — это неудобная модель клиентского кода (неизбежность использования remote facade).

Но даже эта хитрожопость не решает проблем со, скажем, транзакциями между системами.

Короче говоря, no silver bullet there, too. Для многих (в том числе — высоконагруженных) систем подобная архитектура будет не к месту. Именно за счет оверхеда (как в разработке, так и в реальной работе).
>continuousdelivery.com

Спасибо, буду разбираться. Оно конечно и ежу понятно, что нужно автоматизировать. Но пока это кажется не очень то реальным в нашем проекте. Выкладка сервисов и апдейтов к ним — это сложный полуавтоматический процесс, с контролем со стороны человека (релиз менеджера) всего процесса. И то проблем масса. Что бы оно все выкатывалось, а что еще сложнее, апдейтилось, само, кажется очень нереальным.
Ну, это всегда сначала кажется нереальным, особенно в части апдейтов. Но чем больше там автоматизировано, тем лучше.
На одной машине оно не взлетит, да и вообще глупо. В итоге довольно сложная инфраструктура.

Инфраструктура зависит не столько от архитектуры, сколько от размера проекта. Обычно всё отлично работает на одной машине, пока не вырастают нагрузки.

А вот насчёт «и вообще гпупо» — не могу не согласится. Основная идея именно в том, что отдельные части можно разрабатывать независимо друг от друга и не держа в голове весь проект в целом. Если бы при этом требовалось его обязательно устанавливать целиком каждому разработчику — это говорило бы о том, что в реальности все не насколько изолировано, насколько должно было быть, и надо фиксить архитектуру.

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

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

Лично мое субъективное впечатление: сервис-ориентировання архитектура усложняет рефакторинг и как следствие делает систему менее гибкой. Выделять сервис на каждый чих — плохо. Это все равно, что на несколько лет вперед придумать каким-то чудом все интерфейсы, а потом несколько лет писать их реализацию. Я утрирую, конечно, но идея такая.

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

Ну, что тут скажешь… тщательнее надо архитектуру разрабатывать, тщательнее… учиться, учиться и учиться… и будет вам щастье… может быть. :) Кстати, попытки запускать по сервису «на каждый чих» и «на несколько лет вперед придумать каким-то чудом все интерфейсы» обычно говорят о том, что вашему архитектору ещё не хватает опыта в разработке SOA. Мы далеко не сразу решились запустить отдельный сервис для генерации уникальных id, и обычно у нас сервисы более крупные. Фактически, нужно найти баланс между размером сервисов и их количеством. Мы стараемся использовать критерий простоты — задача начинает разделяться на несколько сервисов тогда, когда её не удаётся просто реализовать в одном сервисе.

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

Не совсем так. Во-первых, часто удаётся найти гада просто запустив top и посмотрев, кто у нас жрёт много CPU. Во-вторых, специалисты более высокого класса здесь как раз не нужны — ибо искать проблему в небольшом изолированном сервисе легче, чем в большой цельной системе. Но вот насчёт необходимости просматривать кучи логов разных сервисов чтобы понять что происходит — эта проблема у нас тоже есть. Обычно у нас всё само работает нормально, так что зарываться в логи приходится редко, тьфу-тьфу (хотя может это просто кажется, что всё нормально работает — просто в логи смотрят редко, вот и не знают как на самом деле всё плохо :))! Но если бы эта проблема возникала чаще — пришлось бы искать для неё какое-то более эффективное решение… скорее всего поднятие отдельного сервиса статистики и мониторинга, куда остальные бы регулярно рапортовали что у них происходит.
> необходимость писать новые сервисы под эти интерфейсы, а не переделывать существующие.

Это называется писать костыли :) Ситуация ровно такая же, как я и описал в случае слишком раннего проектирования интерфейсов. Вначале мы бьем себя пяткой в грудь и «проектируем». Потом с эволюцией системы начинаем писать костыли на архитектуру.

> что вашему архитектору ещё не хватает опыта в разработке SOA.

Дело не в этом. Опыт софтверной индустрии показал, что проектирование софта штука такая, если требования постепенно изменяются (а обычно это так), то все равно фиг угадаешь. «Все абстракции протекают». Рулят гибкие методологии, а они плохо ложатся на SOA.

>ибо искать проблему в небольшом изолированном сервисе легче, чем в большой цельной системе.

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

Что касается системных проблем — одно из двух: либо мы до них ещё не доросли, либо у вас системные проблемы вызваны ошибками в архитектуре. Кстати, можно пример таких проблем?
>то новая задача требует нового решения, т.е. новых интерфейсов и сервисов.

Т.е. выходит, если задачу «внести коррективы в задачу» принять за новую задачу, то это как-то оправдывает создание нового костыльного сервиса? :) Есть поддержка, есть новые фичи.

> либо мы до них ещё не доросли, либо у вас системные проблемы вызваны ошибками в архитектуре.

Да, ошибки в архитектуре есть, и они сильно мешают. Да, вы доростете до таких размеров, что у вас ошибки в архитектуре будут тоже :)

проектируем каждый интерфейс как подходящий для (а) сетевого общения, т.е., remote facade…лишаем себя…производительности

Необходимость в remote facade вызвана не сетевым протоколом, а особенностями внутренней реализации сервиса. У нас в проекте до сих пор прибегать к remote facade не приходилось.

Более того, общий подход к архитектуре и особенности реализации сильно зависят друг от друга. Если взять классический пример remote facade, то реализация класса «Address» в виде кучи «мелко-молотых» методов изначально вызвана тем, что этот класс понятия не имел кто и как будет им пользоваться в большом проекте, частью которого он являлся. Если же этот класс изначально пишется не как универсальное решение, а как частное решение для одного из мелких проектиков, задачи которого просты, известны заранее, и в широких пределах изменяться уже не будут (не позволит внешний сетевой интерфейс этого проекта), то обычно необходимости в «мелко-молотых» методах просто не возникает, а значит к ним и remote facade не требуется приделывать.

Что касается производительности, то об этом ниже более подробно.

(б) кроссплатформенный…лишаем себя…возможностей платформы.

Эту логику я не понял. Проекты пишутся не кроссплатформенно, а каждый на своей платформе, поэтому каждый отдельный проект может пользоваться всеми возможностями своей платформы на полную. А сетевой протокол действительно делается кроссплатформенным (что обычно означает — plain text, а-ля json или http), но никаких возможностей это нас не лишает.

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

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

Конкретный пример из нашего проекта. У нас есть достаточно большой поток данных, который необходимо прогонять через цепочку сервисов для обработки. Если обрабатывать весь поток в одном сервисе, то действительно может возникнуть ситуация, когда один сервер не сможет тянуть этот единственный сервис. Но у нас данные таковы, что внутри общего большого потока фактически существует два десятка других, абсолютно независимых друг от друга, которые можно обсчитывать по одному. В результате вместо сервиса, который умеет обсчитывать общий поток данных, был написан сервис, умеющий обсчитывать только один из внутренних потоков данных… и этот сервис одновременно запущен 20 раз, по одному процессу на каждый из имеющихся потоков данных. В результате вопрос масштабирования решён на этапе проектирования, и в коде сервиса ничего специально для поддержки масштабирования нет (более того, от такого подхода код сервиса стал даже проще, т.к. из него ушла логика работы с несколькими потоками данных). За одно, в качестве побочного эффекта, такой подход решил проблему масштабирования на все доступные ядра (многие сервисы написаны на perl, и многопоточность использовать не могут).

Собрать проект и запустить у себя локально могут пара человек в комманде, да и то пройдя огромный путь по конфигурированию всех сервисов
…Идея CD в том, что все необходимое окружение должно выкатываться максимально автоматически. В идеале — «нажал кнопку, получил среду».

Ну, у нас тоже весь проект поставить на машину одного разработчика довольно не просто. Помимо необходимости развернуть и настроить кучу проектов, когда они все стартуют они ещё и кучу памяти кушают (фактически, только у меня установлены почти все сервисы проекта, и при загрузке машина реально стонет секунд 30 пока они все стартанут и сожрут свои 1-2 гига памяти).

Но дело в том, что ставить полностью весь проект разработчикам не нужно! Они устанавливают только те проектики/сервисы, которые разрабатывают. Если им для тестирования на вход нужны данные от других сервисов, которых на машине этого разработчика нет — либо подключаются к нужным сервисам прямо на продакшне, либо доустанавливают нужные сервисы себе на машину, либо пишут простенькие псевдо-сервисы эмулирующие нужную функциональность для тестирования (это не так страшно, как звучит — зачастую хватает запустить netcat на прослушивание порта и выплёвывание json-файла всем, кто подключится).

А вообще требование «нажал кнопку, получил среду» для больших проектов работающих на кластере серверов звучит несколько нереально. Или Вы думаете, что системы гугла или амазона готовы к такому развёртыванию? Отдельные части — наверняка, но не вся система в целом. Так отдельные проекты/сервисы и у нас устанавливаются за 5 минут запуском 3-4 команд.

Там было очень хорошо видно, чего стоит вынос вызова из локального контекста в удаленный. … (грубо говоря, если увеличение латентности за счет выноса в отдельное звено больше, чем уменьшение времени обработки запроса, то мы не выигрываем, а проигрываем) … Вот у вас выше есть пример, когда в отдельный проект (сервис) вынесена генерация уникального id по запросу. Как мы все понимаем, сама по себе эта задача не очень требовательна к ресурсам, и, скорее всего, высокопроизводительна. Очень может быть, что в ее случае накладные расходы на ее функционирование в качестве сервиса (с учетом поддержания кроссплатформенности и возможности легко вынести в другое звено) превышают расходы на собственно ее работу. Это, как можно догадаться, не есть рациональное распределение ресурсов.

Теперь о производительности. Если рассматривать шарообразный сервис в вакууме — Вы абсолютно все правильно описали. Но в реальной жизни помимо сравнения скорости выполнения запроса сервисом со скоростью сетевой коммуникации с этим сервисом, есть ещё другие важные факторы. К примеру, упомянутый мной сервис генерации id используется исключительно при добавлении нового юзера в систему. Как Вы понимаете, новые юзеры добавляются не насколько часто, чтобы «не рационально высокая» латентность выделения ему уникального id хоть как-то сказалась на общей производительности проекта.

Безусловно, факторы latency и bandwidth нельзя совсем не учитывать при разработке архитектуры проекта в этом стиле. Но и критичными они не являются — например, в описании архитектуры Амазона написано, что у них используется 100-150 сервисов при генерации одной веб-странички. У нас в проекте мы тоже пока никак не почувствовали тормозов из-за большого количества сетевых коммуникаций.

Короче говоря, no silver bullet there, too. Для многих (в том числе — высоконагруженных) систем подобная архитектура будет не к месту. Именно за счет оверхеда (как в разработке, так и в реальной работе).

Вообще-то серебрянная пуля существует — это простота. Другое дело, что это знание не очень облегчает достижение оной простоты… но зато понятно, куда нужно стремится.

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

Безусловно, некоторый оверхед присутствует — в каждом сервисе требуется реализовывать поддержку сетевых протоколов, реестра сервисов, публичный интерфейс, подключение и общение по разным протоколам с другими сервисами для получения нужных данных… Но! По моим личным наблюдениям, этот оверхед сначала сводится почти к нулю за счёт однократной реализации необходимой инфраструктуры модулями/библиотеками, после чего единственное оставшееся усложнение заключается в неизбежной асинхронности — вместо вызова локальной функции делается сетевой запрос, ответ на который придёт не сразу, и запросов таких параллельно делается обычно много — так что сервисы необходимо делать либо многопоточными либо событийно-ориентированными (у нас и те и другие — на Limbo многопоточные, на Perl событийные, на Python я точно не знаю, их другой разработчик пишет :)).

В результате весь оверхед выливается в требование определённого стиля программирования. При этом выигрыш в простоте разработки по-настоящему изолированных небольших сервисов перекрывает этот оверхед во много раз. И писать в таком стиле большие проекты оказывается намного легче и быстрее. Насколько легче, что после этого средние и мелкие проекты тоже зачастую пишутся в аналогичном стиле, хотя для человека «не в теме» это выглядит немного диковато.
«то обычно необходимости в «мелко-молотых» методах просто не возникает, а значит к ним и remote facade не требуется приделывать.»
Я в данном случае говорю не о том, что надо приделать remote facade, а о том, что интерфейс каждого сервиса заведомо проектируется с учетом того, что это удаленный доступ. Что накладывает определенные ограничения (и в некоторых местах делает разработку менее удобной).

«Проекты пишутся не кроссплатформенно, а каждый на своей платформе, поэтому каждый отдельный проект может пользоваться всеми возможностями своей платформы на полную. А сетевой протокол действительно делается кроссплатформенным (что обычно означает — plain text, а-ля json или http), но никаких возможностей это нас не лишает.»
Когда вы говорите: «я не знаю, что у меня с той стороны сервиса», вы вынуждены передавать только тот поднабор данных, который будет одинаково воспринят любой платформой. Это ограничение, и существенное.

Один из моих любимых примеров — это remote linq. Ситуация банальна: есть локальный интерфейс доступа к данным, построенный с использованием linq (т.е., по сути, проприетарного диалекта AST). Работа с ним внутри системы проста и прекрасна. Но выставить его наружу — на редкость сложно. Это сложно даже при условии того, что клиент работает в той же платформе; сделать же это для клиента с другой платформы — невозможно. Единственный способ — это написать немаленький адаптер, который будет переводить проприетарный AST в какой-то кроссплатформенный AST, но оверхед этого безумен. В итоге, вместо того, чтобы использовать удобный и понятный интерфейс построения запросов, мы вынуждены придумывать всякие обходные пути. Вот вам и подарок от кроссплатформенности и универсального сетевого протокола.

«Конкретный пример из нашего проекта. „
Теперь попробуйте “схлопнуть» этот пример обратно — то есть, обеспечить сопоставимую производительность на маленьком потоке данных с использованием пропорционально меньшего оборудования. Удалось?

«А вообще требование «нажал кнопку, получил среду» для больших проектов работающих на кластере серверов звучит несколько нереально.»
Расскажите это авторам книжки. Честное слово, там описано много подходов и способов решения описанной вами проблемы; не вижу смысла это здесь пересказывать.

«Так отдельные проекты/сервисы и у нас устанавливаются за 5 минут запуском 3-4 команд.»
Разница в подходе. Должны устанавливаться запуском одной.

«У нас в проекте мы тоже пока никак не почувствовали тормозов из-за большого количества сетевых коммуникаций.»
Специфика проекта? Специфика потока данных?

Мы в свое время делали у себя простенькую проверку: одна и та же машина, обращение к СУБД напрямую (через ДАЛ в том же процессе) и через самый простой boundary, который может быть (DAL в соседнем процессе). Оверхед — две секунды (при времени выполнения менее 0.1 секунды). Вот и выходит совершенно тривиальный выбор: или мы делаем систему, которая будет работать быстро сейчас, или мы делаем систему, которая сейчас будет работать медленно, но зато впоследствии, при добавлении отдельного сервера, может быть, не станет работать медленнее (причем нет никаких гарантий, что это добавлеие отдельного сервера вообще случится). Тот самый случай premature optimization.

(предвосхищая вопросы: нет, написать так, чтобы системе было вообще все равно, крутится DAL в том же процессе, или в другом — нельзя, мы теряем половину скорости разработки и половину нативного функционала DAL, смысл?)

«Что касается оверхеда в разработке, то добавлять новые фичи в виде мелких отдельных сервисов намного быстрее и легче, чем дописывать их внутри большого цельного проекта — во-первых не требуется знать и держать в голове весь проект, и во-вторых от добавления нового сервиса существующие точно не упадут и не начнут глючить.»
Ну, здесь вы не правы. В любой адекватно написанной системе добавление новой изолированной фичи не приводит к падению старых (а если она не изолированная, вы не сможете вынести ее в отдельный сервис).
Теперь попробуйте “схлопнуть» этот пример обратно — то есть, обеспечить сопоставимую производительность на маленьком потоке данных с использованием пропорционально меньшего оборудования. Удалось?

Будет день, и будет пища. Разные задачи/ситуации требуют разных подходов. Несложно придумать виртуальную проблему с виртуальными ограничениями, из которых будет вытекать один единственный способ её решить. С реальными проблемами и реальными ограничениями всё совсем иначе — как правило всегда можно либо задачу немного изменить, либо некоторые ограничения обойти, либо вообще посмотреть на проблему под нестандартным углом… И всегда у всего есть конкретная стоимость: зачастую купить более дорогое оборудование ради сохранения простоты реализации проекта получается намного дешевле, чем значительно усложнять и оптимизировать проект ради достижения требуемой производительности на более дешёвом оборудовании.

Разница в подходе. Должны устанавливаться запуском одной.

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

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

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

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

Кроме того, возвращаясь к производительности, в наши дни гораздо важнее иметь возможность ускорить работу проекта просто добавив серверов/cpu/ядер, нежели используя очень дорогие и быстрые cpu/hdd на одной машине.

Ситуация банальна: есть локальный интерфейс доступа к данным, построенный с использованием linq (т.е., по сути, проприетарного диалекта AST)…обращение к СУБД напрямую (через ДАЛ в том же процессе) и через самый простой boundary, который может быть (DAL в соседнем процессе)

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

Если забыть о технологиях Microsoft, и взять самый обычный DAL на Perl/Linux/MySQL, то мы тоже проводили такие тесты. Но у нас никакого заметного замедления работы с базой не было! Безусловно, запросы типа «SELECT 1+1;» выполнялись значительно (примерно в два раза, точно не помню) дольше, но когда речь шла о нормальных запросах, то дополнительные издержки на общение с базой через промежуточный процесс полностью терялись на фоне времени выполнения самого запроса. И это абсолютно логично, т.к. стандартное «прямое» общение с сервером БД и так идёт исключительно по сетевому протоколу, а мы просто добавили ещё одно сетевое подключение в цепочку. Вероятно, если бы движок БД был слинкован в наше приложение и «прямой» доступ к БД сеть не использовал, разница в скорости была бы более заметна — в абсолютных числах, но не в конечной производительности, т.к. время на выполнение обычного запроса в базу всё-равно намного дольше времени на передачу этого запроса по сети (особенно на localhost).
«И всегда у всего есть конкретная стоимость: зачастую купить более дорогое оборудование ради сохранения простоты реализации проекта получается намного дешевле, чем значительно усложнять и оптимизировать проект ради достижения требуемой производительности на более дешёвом оборудовании.»
Мне кажется, вы не увидели одного важного пункта — «на _маленьком_ потоке данных». Т.е., речь не о виртуальных ограничениях, а о совершенно типовой ситуации, когда есть проект с не очень большой нагрузкой (т.е., такой, которую тянет однозвенная архитектура), увеличения этой нагрузки не планируется, и денег на более дорогое оборудование — тоже.

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

Я, собственно, говорю только о том, что такая архитектура хороша не для всего, все-таки.

«А одновременная установка всех сервисов проекта на кластер в жизни абсолютного большинства проектов случается один раз. И, вспоминая о стоимости, обычно дешевле оплатить один раз несколько дней работы админа на эту установку чем несколько месяцев работы программистов на автоматизацию этой установки.»
А вы книжку-то прочитайте, прочитайте. Я не издеваюсь, просто вы озвучиваете аккурат куски из нее, и мне как-то не хочется кусками из нее же их объяснять.

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

«Это всё не принципиально.»
Это как раз очень принципиально, на самом деле, потому что это та грань, которая отделяет SOA от обычной разработки. Все те вещи, которые я писал в самом начале, характерны именно для SOA, потому что вытекают из (а) требований к интероперабильности и (б) независимости от звена расположения.

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

«Кроме того, возвращаясь к производительности, в наши дни гораздо важнее иметь возможность ускорить работу проекта»
Беда в том, что для разных проектов важно разное. Я уже описывал выше сценарий, обратный вашему.

«Могу только сказать, что это не нормальная ситуация — либо Вы что-то нахомутали при тестировании, либо пытались разделить проект сетевыми протоколами на части в неподходящих для этого разделения местах.»
Ну, место разделения-то очевидно — между BL и DAL, самое то для деления в многозвенке. А ненормальность этой ситуации только в том, что мы хотели сохранить на стороне BL удобный (объектно-ориентированный) интерфейс доступа к данным. У SOA, кстати, вообще тяжело с адекватной объектно-ориентированной моделью между сервисами.

«И это абсолютно логично, т.к. стандартное «прямое» общение с сервером БД и так идёт исключительно по сетевому протоколу, а мы просто добавили ещё одно сетевое подключение в цепочку.»
А у вас там не было заодно участка «теперь соберем из данных объектную иерархию — сериализуем эту иерархию (сожрав где-то в 5-10 раз больше памяти) — передадим сериализованное — десериализуем»? Вот он как раз все и жрет.

«т.к. время на выполнение обычного запроса в базу всё-равно намного дольше времени на передачу этого запроса по сети (особенно на localhost).»
Это, очевидно, зависит от того, маленький у вас запрос, или большой. Потому что на хорошей базе скорость выборки большого запроса может оказаться сопоставимой со скоростью чтения диска, а та, понятное дело, заведомо больше скорости сетевого интерфейса.
Насчёт маленького потока данных — согласен. Собственно, если обратиться к моему первому комментарию выше, то там речь шла именно о «больших и сложных» проектах. Наверное, существуют большие и сложные проекты, которые запускаются на одной машине раз в месяц для пакетной обработки данных и отрабатывают за час — их явно не стоит писать на SOA. :) Но в мире веб-разработки «большой и сложный» проект автоматически означает, что нагрузка зависит от популярности проекта и что готовность проекта переехать в любой момент с одного сервера на кластер — это большой плюс, даже если пока большой нагрузки не планируется.

Книжку почитаю. Хотя никакой трагедии в том, что админ действительно нужен на весь срок проекта я не вижу — если есть сервер, то нужен и админ, безотносительно того, занимается он установкой проектов или нет — кто-то должен обновлять ОС, следить за безопасностью, настраивать почту/фильтры/etc., да и просто решать периодически возникающие вопросы. Так что если он ещё и один раз проект установит, от него не отвалится.

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

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

Откуда такая информация? Тест писать лениво, если немного погуглить, то: на линухе tcp-сокеты (c TCP window 512K) на localhost дают от 7Gbit/sec, по локальной гигабитной сети 600-700 Mbit/sec). Короче, в реальных условиях тормозов здесь быть не должно!

А ненормальность этой ситуации только в том, что мы хотели сохранить на стороне BL удобный (объектно-ориентированный) интерфейс доступа к данным. У SOA, кстати, вообще тяжело с адекватной объектно-ориентированной моделью между сервисами.

Это многое объясняет. Все эти «удобные ОО-интерфейсы» и прочие ORM-слои значительно уменьшают производительность, и в высоконагруженных проектах от них как правило вынуждены отказываться. Я вообще ничего такого не использую принципиально — мне не нравится идея «якобы» упрощать приложение путём добавления большого, сложного и тормозящего слоя в критическом месте (доступ к данным). ORM — типичный пример очередной «протекающей» абстракции. Как правило такие вещи упрощают и ускоряют начальную разработку проекта, но за это в больших проектах приходится довольно дорого расплачиваться позднее.
«Насчёт маленького потока данных — согласен. Собственно, если обратиться к моему первому комментарию выше, то там речь шла именно о «больших и сложных» проектах.»
Большой и сложный — не обязательно высоконагруженный. Многие LOB-системы крупны, сложны, но нагрузка там смешная по меркам даже веба.

«Так что если он ещё и один раз проект установит, от него не отвалится.»
Проблема в том, что если проект устанавливается сложно, то админ может напортачить. И это создает лишний риск. Собственно, идея continuos delivery в том, чтобы маскимально избавить нас от этого риска.

«Тест писать лениво, если немного погуглить, то: на линухе tcp-сокеты (c TCP window 512K) на localhost дают от 7Gbit/sec, по локальной гигабитной сети 600-700 Mbit/sec).»
Я боюсь, что в реальной жизни никаких 7Gb не будет (скорость, все-таки, ограничивается многими интересными вещами, включая CPU и память, которые базой уже сожраны). А как только речь идет о локальной сети, так надо помнить, что скорость сата-интерфейса — до 3Гбит, а если у нас там SAN на оптике, так и еще веселее.

«Все эти «удобные ОО-интерфейсы» и прочие ORM-слои значительно уменьшают производительность, и в высоконагруженных проектах от них как правило вынуждены отказываться.»
Вы снова скатываетесь к высоконагруженному проекту. У них совершенно свои правила существования. А есть еще категория приложений, вся сложность которых — это вывернутая (и постоянно меняющаяся) доменная область. И здесь без ОО-парадигмы (на всех уровнях приложения, с максимумом трассировки изменений при сборке) очень тяжело. Но, как бы, даром что они не высоконагруженные, они, тем не менее, очень сложные и нетривиальные в проектировании.

Вот и возвращаемся к началу: SOA хороша не для всего и не всегда.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории