Интересно. Я всегда считал, что CAP — это некий общий принцип, который не вдается в частности организации системы, а его интерпретация немножко другая.
Availability — это именно обработка запроса с минимальной задержкой, нежели просто доступность данных. В распределенной системе невозможно одновременно Consistency и Availability по причине того, что для «C» необходим координированный доступ к данным (через блокировки или MVCC), которые в распределенной среде занимают большое время (необходим ответ от всех затронутых нодов). То есть для чтения либо жертвуем «А» и ждем, когда коммитнется транзакция, либо жертвуем «C» и читаем неполные данные (при асинхронной репликации).
Если CAP затрагивает только аспект сетевого fault tolerance, но не latency, то она действительно очень ограничена.
P.S. как я понял, линеаризируемость — это перевод «serializable»?
Обычно для фронтенда (под ним мы понимаем удаленного пользовательского клиента) делают бекенд, с которым он будет эксклюзивно общаться по специально заточенному для этого клиента интерфейсу. А уже бекенд в свою очередь разруливает запросы по корпоративным системам. Более того, чтобы ускорить работу и не напрягать системы бесполезными запросами, (коих больше 95%) зачастую бекенд содержит кеш всех требуемых клиентом данных, который обновляется асинхронно через месседжи. То есть по сути бекенд содержит копию данных корпоративных систем, зачастую в денормализированном виде.
Клиенты, которые сами ломятся в несколько корпоративных систем за данными, могут быть использованы только внутри корпоративной сети, и то редкость. Выставлять «наружу» пол корпорации чревато проблемами security.
SOA как таковая вообще бесполезна. Есть база данных или даже несколько. Базы данных — это наше все. Как они соединены через скольких посредников — это уже тема интеграторов. Многие вообще используют DBLink и радуются. Многие обмениваются файлами, обычно ночью.
Естественный путь создания сложной модульной системы через интеграцию сервисов от разных поставщиков независимо от платформы и технологии.
Технологии и их интеграция — не проблема. Webservices — не единственная кроссплатформенная технология. Есть CORBA, к базе данных есть коннекторы практически к любой платформе. Передать и получить данные не пробема — хоть через MQ, хоть через Socket, хоть через email. Нет коннектора — сделай shared directory. Файлы умеют читать все.
Подталкивает к слабосвязанным системам, помогает работать со старыми системами.
Сомнительная помощь, просто еще один посреднеческий уровень. Системы среднесвязаны на уровне контракта. SOAP/Http еще и подталкивает к сильной связанности, т.к. binding и адрес сервиса жестко прописывается в самом WSDL. Конечно, это можно (и нужно) обойти, но есть кадры, которые для этого меняют IP в hosts, а для импорта WSDL требуют задеплоить и запустить сервис.
Повышает эффективность путем повторного использования сервисов, что снижает расходы и время разработки
Теория. На практике каждый WSDL делается и затачивается практически для одного потребителя.
Улучшает гибкость и масштабируемость, потому что многие сервисы могут быть разработаны с помощью различной компоновки существующих приложений.
Опять теория. Для доступа к базе данных между разными вызовами WS отсутствует целостность (кто-нибудь пробовал поднять транзакционный WS? это адище!). Фактически в большинстве случаев компоновка продьюсер-консумер 1:1. Особенно, если вы используете очереди JMS.
Делает возможным сокращение расходов, связанных с поддержкой решения.
Ложь и провокация! Придется оплачивать недешевый дополнительный уровень с комадной бестолковых бездельников. Использование SOA само по себе создает новые проблемы, которых бы не было без нее, начиная от постоянных ошибок в меппинге данных и кончая сложными отказами SOA-продуктов, за которыми приходится стучаться в службу поддержки. Один из недостатков SOA — огромные ресурсозатраты при сомнительном выигрыше. Решения SOA стоят занебесных цен (Oracle SOA Suite, IBM Websphere Business Integration). Специалисты по этим системам стоят дороже, нежели core-разработчики. Разработка, как ни странно, усложняется в разы, а тестирование систем — в десятки раз. Плюс, все решения глючные, неповоротливые и черезмерно навороченные.
Можно разработать стандартный протокол общения между системами.
Есть такой велосипед. Обычно добавляют хидеры для роутинга сообщения и адрес для callback. Однако всегда есть очень дорогой и понтовый «legacy систем», который будет класть на весь ваш SOA и протокол общения.
Доступ к данным не зависит от географии пользователя, можно использовать мобильные средства связи
SOA не предназначена для фронтендов. Это корпоративная архитектура. Для фронтендов обычно делают легкий бекенд.
Возможность постепенного ввода в строй частей системы, что позволяет быстрее реагировать на нужды бизнеса.
Вырезка из PowerPoint презентации… Что мешает делать это без SOA?
Ну, если судить абстрактно, то вообще существует только eventual consistency. Просто транзакционная модель контролирует многие аспекты, среди которых — «поймать» этот момент consistency, гарантировать атомарность, snapshot-чтение данных, etc… Проблема в том, что в распределенной среде синхронизация данных осуществляются медленно (хотя все относительно — игровые серверы живут и процветают), и люди делают выбор в пользу BASE и асинхронной архитектуры. Но это всегда в разы усложняет логику, поскольку контроль над выборочной consistency полностью ложится на разработчика. Поэтому «реактивная» парадигма — это не панацея и не новый модный подход, а вынужденная необходимость. Там, где можно использовать ACID, нужно использовать ACID.
P.S. Да. Но чтобы добиться eventual consistency банки устраивали «учетный день» или даже «учетную неделю», когда денежные операции не принимались, или складывались в очередь, но не выполнялись. В современном мире невозможно, хотя в Европе большинство банков работают с клиентами до 14:00, а дальше, видимо, время eventual consistency :) Говорят, что и рождественские каникулы существуют для того, чтобы спокойно привести годовой баланс в порядок и закрыть бюджет :)
Насчет примера: любой банк. Каждый день в XX:YY часов открывается окно для межбанковских операций. Нужно посчитать к этому моменту все операции за день и изменения счетов, и выставить суммарный счет каждому из контрагентов. К этому моменту нужно, чтобы все операции были завершены и состояние всей БД было стабильно.
Дугой пример из того же банка. Сделали асинхронные транзакции, по одному актору на один счет. Все шустро и независимо работает: сообщения о денежных переводах роутятся к акторам, каждый актор смотрит баланс своего счета, и если он становится негативным, то операция запрещается. Теперь менеджер захотел, чтобы для определенных клиентов операция разрешалась, если суммарный баланс всех счетов одного клиента был бы неотрицательный, тогда как каждый счет в отдельности может стать негативным. Можно сделать новый тип акторов, который бы пропускал через себя все транзакции к счетам каждого клиента, но это потребует архитектурных изменений и затруднит совмесную работу со старыми акторами. Возвращаемся к блокировкам.
В CQRS моделях набор выполняемых операций ограничен, операции достаточно просты, чтобы быть выполненными внутри одного аггрегатора, а логика их выполнения не зависит от выполнения других операций или данных, лежащих вне аггрегатора. Для многих бизнес задач операция требует синхронного изменения сразу многих объектов. Выделить операции в атомарные акторы не представляется возможным, так как одни и те же данные используются разными операциями. Поэтому приходится искусственно создавать блокировки, либо использовать уже готовые средства, предоставляемые dbms.
Если я ничего не перепутал, то для решения подобных проблем используются conflict-free replicated data types
Необходим конечный автомат с персистентным состоянием, который бы ловил эти события, коррелировал бы их в группы и применял бы какое-нибудь правило типа: если получили A, B, C (в любом порядке), то выкинуть X. Если получили A, C, D, то выкинуть Y, etc…
Всегда возникали вопросы по архитектуре реативного приложения. Насколько я понимаю, реактивное программирование и CQRS предназначены для простой бизнес логики и слабо связанных данных. Основными проблемами данного подхода я вижу:
— невозможность свободной выборки данных из разных entity (то, что в ER делают JOIN-ы). Как выход предлагается денормализировать читаемые данные ввиде графа, чтобы выборка происходила по одной entity, которая включала бы в себя все необходимое. Но это далеко не всегда достижимо.
— отсутствие стабильного состояния в любой момент времени. То есть полное состояние — это то, что лежит в базе плюс все event-ы, разбросанные в данный момент по обработчикам. Как быть, если наш запрос — нечто более сложное, нежели атомарное изменение, а например: прочитать данные, проанализировать и в зависимости от этого выполнить ряд изменений разных сущностей? Без локов или транзакций состояние будет «уплывать», что ведет к потере целостности.
— большая сложность групповой обработки событий, например если нужно объединить несколько событий в одно большое. При декомпоновке событий очередность обработки не гарантируется.
> Просто интересно, какие именно части спринга вы имеете ввиду
Как я уже сказал, IoC — с точки зрения архитектуры концепт достаточно сомнительный. Организация архитектуры более традиционными способами имеет преимущества. Если все же нужен IoC, возьмите Guice — это мелкий IoC контейнер с отличной конфигурацией через DSL. Затем, в реальном проекте очень быстро понимаешь, что и в нем нет необходимости: связать пару бинов можно и ручками.
Затем идет пресловутый Spring MVC. Благодаря Struts и куче выросших на нем кодописунов, он быстро проник в массы и стал стандартом де-факто. Идея была понятна, но совершенно непроработана. В итоге, использование MVC на порядки усложнило разработку. Кроме того, MVC легко позволяет делать плохо, что в неумелых руках ведет к полной дезорганизации. Могу сказать, что в 90% сайтов хватит Servlet + JSP + JSTL, про которые все как-то очень быстро забыли.
Для справки, Apache Tapestry — гораздо более проработанный и организованный фреймворк, нежели Spring MVC.
Spring Data — совершенно рудиментарен. Запросы к базе данных в реальном приложении — это нечто более, чем найти объект в таблице по полям. Repository-per-entity — совершенно глупый концепт, который заставляет пользователя делать JOIN вручную, увеличивая в N раз число запросов к БД.
Spring Hateoas — искусственный концепт экспортировать Spring Data через Rest. Те же проблемы, только еще с участием HTTP.
Т.о. большинство концептов Spring-а искусственны и годны только в теории и для демок.
> Не совсем понял, что означает «заточка веб тольк очерез mvc»
Я об IoC и его scopes. В spring определен request scope, который сохраняет состояние объектов во время запроса. Однако его использование ограничено исключительно MVC контейнером. Когда запросы приходят, например, через JMS или RMI, придется разрабатывать и интегрировать свой scope. Сделать абстрактный request scope для всех endpoint-ов разработчики не догадались. Кроме того, MVC включено в Core Spring Framework, а не существует как отдельный проект, что вместе с другими технологиями делает понятным целевое использование фреймворка.
Вообще согласен, спорить особо не о чем. Spring предлагает большой инструментарий для разработки и интеграции. При хорошем владении и умелом использовании можно достичь хорошего качества продукта.
Плохо, что Spring стал своего рода стандартом де-факто. Его лепят везде, даже не задумываясь о целесообразности. В большинстве проектов, которые я видел, Spring рудиментарен и проще и лучше было бы обойтись без него, чем с ним, заменяя легковесными библиотеками с целевым функционалом. Велосипеды легче вести, контролировать и кастомизировать. Они всегда более предсказуемые и легко отлаживаемое. Сколько раз приходилось биться в истерике, когда логически вроде бы все должно работать, но фреймворк либо кидает несколько страниц страшного эксепшна, либо еще хуже — просто игнорирует написаное или работает не как ожидалось? Это бич всех фреймворщиков.
Насчет неплохого решения с архитектурной точки зрения я мог бы долго спорить. Тут и проблемы изначальной заточки только под разработку веб через MVC, и сомнительность самой парадигмы IoC, и куча совершенно ненужных прослоек, типа Spring Data, и тупо неудобство многих вещей, etc…
Не согласен. Это скорей не цель, а вынужденная необходимость. Из мира Java есть хороший пример — Spring. Тоже начинался как простенький IoC + MVC и очень быстро опух до неудобоваримого состояния. Для интеграции фреймворк определяет рудиментарные прослойки, которые сильно ограничивают и усложняют использование. То есть надо знать и саму низлежашую технологию, и прослойку, и как ее обойти при необходимости. Плюс, следуя сомнительной идее как можно больше бойлерплейта замести «под ковер», фреймворк определяет кучу неявных преобразований, умолчаний и деклараций: у фреймворка появляется магия и заклинания для вызова демонов. Очень быстро начинаешь понимать, что фреймворк концентрирует на себе, а не на решаемой задаче: решение из понятного «просто решить задачу» превращается в сложную и неопределенную «решить задачу с помощью фреймворка». Такое впечатление, что единственный оптимизируемый ресурс — это строки кода, которые сделаны из золота.
P.S. апофеоз «рамочной» инженерной мысли: Spring Batch с документацией в 250 страниц о том как написать скрипт для загрузки файла в базу данных.
Многие путают фреймворк и библиотеку. Это принципиально разные вещи. Отличие состоит в том, что библиотека расширяет возможности языка, тогда как фреймворк наоборот, ограничивает возможности написания кода установленными парадигмами.
Как правило фреймворк не имеет технической возможности не дать пользователю использовать «некорректные» для этого фреймворка конструкции. Поэтому фреймворк устанавливает ряд соглашений или спецификаций: типа так можно делать, а так нельзя. Спецификации всегда неполны и оставляют за кадром львиную долю неясностей и неопределенностей. Поэтому поведение большинства фреймворков описательно недетерминированно: в отличие от языка программирования, пользуясь только спецификацией, нельзя определить конечное поведение программы. Чтобы что-то написать, приходится изучать все тонкости внутренней имплементации фреймворка.
Каждый фреймворк предлагает набор шаблонов как «правильно» будет сделать ту или иную задачу. Типичные задачи решаются просто при помощи «магии»: декларативных конструкций фреймворка. Если фреймворк где-то не покрывает требуемый функционал, то найти обходное решение будет весьма сложным делом. Я видел проект, где функционал на 50% состоит из всевозможных подпорок и work-around-ов.
Фреймворки очень плохо интегрируются друг с другом, если эта интеграция не предусмотрена функционалом. В отличие от библиотек, каждая из которых ориентирована на конкретную задачу, и которые без проблем могут использоваться вместе в одном проекте, фреймворки вынуждены пухнуть и вмещать в себя сразу весь требуемый функционал.
Не совсем понятны утверждения насчет некрасивости? Тема Valo вполне неплохо смотрится и легко кастомизируется. Reindeer тоже ничего, за исключением кнопок. У приложений единый лук, стиль везде выдержан.
Избыточностью.
Во-первых, задолбает писать для каждого поля safe-геттер. IDE такое не предлагает.
Во-вторых, если наша задача — минимизировать возможность ошибки, то здесь остается такая дверь ввиде традиционного геттера. Если я сделаю такой бин, угадайте какой из двух геттеров в 90% случаев будет использовать Вася?
Во-третьих, никто так не делает. Ни одна спецификация, рекомендация, или просто use-case не содержит подобного. Все библиотеки, фреймворки, работающие со свойствами, будут использовать традиционные аксессоры.
И, наконец, есть решение лучше и проще:
class MyBean {
@Nullable
private String value;
// а это сгенерит умная IDE
@Nullable
public String getValue() {return value;}
public void setValue(@Nullable value) {this.value = value;}
}
Я плохо выразился. Имелось ввиду, что там, где возвращается Optional можно сделать вывод, что значение nullable, тогда как там, где возвращается не-Optional никакого вывода сделать нельзя.
// мне нравятся Optinal и теперь я буду их везде использовать
public Optional<String> getMyValue();
// а это мой старый код, который используется в куче мест и мне лень все это переписывать. Может ли он возвращать nullable, я уже и забыл.
public String getMyAnotherValue();
// а это код Васи, который поклялся, что тоже будет везде использовать Optional, но почему-то здесь иногда возвращает null.
public String getHisValue();
Проблема nullable не решается Optional, потому что его целью является ссылочный тип который изначально nullable по определению. Лишь дополнительно напрягает программиста распаковкой-запаковкой значений, и проверками, в которых как правило в 90% случаев нет необходимости.
Решение проблемы заключается в обратном: указать, что значение как раз не является nullable и оно safe для операций. Если идти путем Optional, то потребуется монада Some, и всего лишь переписать весь код, заменяя все non-nullable свободные ссылочные типы T на Some. Делов-то! Либо использовать другой способ, чтобы научить компилятор отличать non-nullable значения от всех остальных, например при помощи аннотаций. Или ввести в язык value-types.
P.S. Для любителей геморроя с Optional:
class MyBean {
// надо optional поле
private Optional<String> value;
// это сгенерит любая IDE
public Optional<String> getValue() {return value;}
public void setValue(Optional<String> value) {this.value = value;}
}
// где-то в коде
MyBean b = new MyBean();
// так можно, компилятор не матюгнется
b.setValue(null);
b.getValue().orElse(""); // NPE
// теперь сделаем все корректно:
class MyBean {
private String value; // чтобы bean был Serializable поля не должны быть Optional
// для каждого поля надо доработать аксессоры ручками
public Optional<String> getValue() {return Optional.ofNullable(value);}
public void setValue(Optional<String> value) {
if (value == null) this.value = null; // можно кинуть NPE в этом случае
this.value = value.orElse(null);
}
}
В Kotlin и подобных языках приведение nullables удобно закамуфлировано. Например, без лишних извратов можно передать non-null значение в функцию, принимающую nullable параметры.
fun doSomething(param:String?);
val s : String = "aaa";
doSomething(s);
Равно как и значение функции, возвращающей non-null значения, может быть присвоено nullable переменной. Плюс функция, возвращающая String? ковариантна к String, т.е. при переопределении можно String? заменить на String:
open class Class1 {
open fun doSomething():String?;
}
open class Class2 : Class1() {
override open fun doSomething():String;
}
Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc. В итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти. Так что я считаю Optional неудачной попыткой исправить nullable косяк при помощи системы типов, нежели изменениями в языке/компиляторе.
Optional в Java — это по большей части пятое колесо, причем недоработанное.
Во-первых, он не предназначен для хранения данных. Optional не имлементирует Serializable, что делает невозможным использование в remote-интерфейсах, и persistence объектах. Поля объекта не рекомендуется делать Optionak. Также не рекомендуется использовать Optional в списках. Насчет аксессоров нет полной определенности, Java Beans спецификация обходит этот вопрос. До сих пор не утихают споры по поводу использования Optional. Вот хороший пост на этот счет: stackoverflow.com/questions/24547673/why-java-util-optional-is-not-serializable-how-to-serialize-the-object-with-suc
Во-вторых, основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer, как например String? в Kotlin, что делает несовместимой всю семантику операций. Чтобы вставить поддержку Optional нужно перелопатить весь код ради сомнительной выгоды. В случае библиотеки, нужно выпускать радикально новую версию API.
В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.
В-четвертых. Практически нет библиотек или API, которые использовли бы Optional. Даже сам Java RT API.
Optional — это чисто ФП фича, который пришел в Java вместе с лямбдами и прочей ФП-лабудой, и в традиционном контексте не имеет большого смысла. Есть более успешная попытка искоренить null в Java при помощи аннотаций @Nonnull/@Nullable, ее и нужно придерживаться. Что реально может решить проблему, так это value types, но это пока еще proposals.
Поправочка: persistence.xml описывает лишь контекст, а меппинги сущностей делаются в orm.xml.
Как уже заметили, Spring Boot предлагает уже готовые связки с авто конфигурацией контекста, хотя усложняет в 10 раз что-либо изменить или добавить. Если же нужно в продакшне деплоить на сервер, то лучше использовать именно Stand-alone Jetty+Spring.
Заранее оговорюсь, что не хочу спровозировать конфликт на почве религиозных разногласий, все это мое персональное мнение.
Scala изначально позиционировался как универсальный язык для всего. Основными целями было:
1. Создать гибкий инструмент, который бы позволил использовать вместе различные парадигмы.
2. Совместимость с Java.
3. Максимальная лаконичность записи. Ничего лишнего.
4. Создание новых синтаксических конструкций через DSL.
5. Наиболее полная система типов.
6. Стать новым IT стандартом.
Считалось, что эти ништяки станут привлекательным для большинства программистов и заставят их пересесть на новый велосипед. Плюс всевозможная реклама подстегивала интерес. Что получилось реально:
1. Различные парадигмы плохо сочетаются друг с другом. Кроме того, использовать в коде сразу несколько парадигм делает код запутанным, а сам язык увеличивает свой «порог вхождения». В 95% случаев функциональный подход используют для лямбд и работе с коллекциями: это модная фича, а программеры уже приучены. Функциональный подход при проектировании приложения встречается крайне редко. В итоге язык становится неким монстром Франкенштейна из плохо сшитых частей разных тел.
2. С дополнительными костылями. Коллекции несовместимы, структуры другие. Постоянно нужно держать в уме во что это будет компилиться. Учитывая что 99% библиотек и фреймворков написаны на Java, это дополнительная сложность.
3. Вообще странно. С одной стороны в Scala есть длинные ключевые слова, которые можно было бы сократить, с другой стороны, вся стардартная библиотека изобилует злоупотреблениями ввиде операторов. Вместо английского названия метода, объясняющего операцию, стоит одна или несколько закорючек, интуитивно не понятных без документации. В угоду DSL был окончательно испорчен синтаксис, позволяющий записывать одно и то же разными способами. Многие называют это гибкостью, когда это больше похоже на отсутствие костей. Сомнительная идея выражений через вызовы методов порождают сложные правила применения операций. В итоге, чем короче хочется написать, тем больше приходится нагружать язык синтаксической магией. Многие конструкции, типа XML, совершенно рудиментарные и искусственно внесены в язык, чтобы где-то сократить написание.
4. Удобство DSL — спорная идея, т.к. привносит магию. Обычно DSL используются для создания builder-а для сложного объекта по типу конфигурации. Попытка расширять конструкции языка через DSL сильно искусственна. Поэтому создатели Scala в последнее время смотрят в сторону макросов Nemerle.
5. А реальный смысл делать систему типов 100% type-safe и настолько сложной? Обычно в редких случаях, где появляется unchecked type casting, программист сам неплохо контролирует это. Как-то же программируют и на языках с динамической типизацией.
6. Несмотря на рекламу, Scala слабо адаптируется в среде программистов. Каждый обязан ее попробовать, но очень скоро вау-эффект пройдет. Создатели Scala ориентированы на совершенно других задачах, нежели требуется в реальных проектах. Скоро это поняли и создали инициативу typesafe.com. Однако покрываемая typesafe.com предметная область сильно ограничена. То есть, либо ты должен использовать Java фреймворк, что только усложняет ситуацию, либо шлепать функционал самому.
P.S. Если цитировать гения «Make it as simple as possible but not simpler», то Scala не может считаться гениальным языком :)
> Видимо поэтому, разработчики ОРМ отдают это на откуп разработчикам приложений.
В том-то и дело, что не отдают. В JPA нет хорошей возможности сказать что конкретно и как доставать. До JPA 2.1 не было даже стандартного способа указать, какие атрибуты мне нужны, а какие нет. Были vendor-specific query hints, и работали через пень-колоду. А основная N+1 проблема до сих пор не имеет решения: JOIN FETCH присоединяет только одну коллекцию. В EclipseLink есть batch hint, который внезапно не работает для ManyToOne и OneToOne.
Видимо, разработчики JPA надеялись, что у нас будет один большой кеш, где будет лежать 80% данных всей базы, с объектами которого будет работать приложение, потихоньку подгружая недостающие части. Однако, как показывает практика, любая страничка с простой таблицей начисто рушит данный подход.
Использую Eclipselink вместо Hibernate, он проксирует только на уровне списков. Но в целом проблемы те же: что делать с lazy объектами вне сессии, и как избавиться от N+1 запросов. Connected-архитектура и lazy инициализация — это огромный антипаттерн, который лимитирует возможность использования объектов только внутри сессии, постоянно напрягая БД огромным количеством тупых запросов. И до сих пор создатели JPA не предусмотрели хорошего способа для обхода ситуации — видимо те, кто пишут JSR, ориентируются на сферического коня в вакууме.
Мы делаем так: при вызове сервиса сначала вытаскивается все, что нужно и только то, что нужно одним или несколькими запросами. Для избавления N+1 запросов можно использовать join fetch или batch-fetch. В JPA 2.1 добавили EntityGraphs позволяющие более просто указывать relations, которые надо вытащить при запросе. Плюс есть нестандартные load-groups и fetch-groups. Если поле lazy и не проинициализировано, оно не должно использоваться. Затем service interceptor прогоняет граф через специальный фильтр, который пробегает все поля, обнуляя непроинициализированные прокси и заменяя проинициализированные коллекции на ArrayList и LinkedHashMap. На выходе получается полностью портабельный detached граф объектов. Почему EntityManager.detach() не делает то же самое — для меня загадка.
Availability — это именно обработка запроса с минимальной задержкой, нежели просто доступность данных. В распределенной системе невозможно одновременно Consistency и Availability по причине того, что для «C» необходим координированный доступ к данным (через блокировки или MVCC), которые в распределенной среде занимают большое время (необходим ответ от всех затронутых нодов). То есть для чтения либо жертвуем «А» и ждем, когда коммитнется транзакция, либо жертвуем «C» и читаем неполные данные (при асинхронной репликации).
Если CAP затрагивает только аспект сетевого fault tolerance, но не latency, то она действительно очень ограничена.
P.S. как я понял, линеаризируемость — это перевод «serializable»?
Клиенты, которые сами ломятся в несколько корпоративных систем за данными, могут быть использованы только внутри корпоративной сети, и то редкость. Выставлять «наружу» пол корпорации чревато проблемами security.
Технологии и их интеграция — не проблема. Webservices — не единственная кроссплатформенная технология. Есть CORBA, к базе данных есть коннекторы практически к любой платформе. Передать и получить данные не пробема — хоть через MQ, хоть через Socket, хоть через email. Нет коннектора — сделай shared directory. Файлы умеют читать все.
Сомнительная помощь, просто еще один посреднеческий уровень. Системы среднесвязаны на уровне контракта. SOAP/Http еще и подталкивает к сильной связанности, т.к. binding и адрес сервиса жестко прописывается в самом WSDL. Конечно, это можно (и нужно) обойти, но есть кадры, которые для этого меняют IP в hosts, а для импорта WSDL требуют задеплоить и запустить сервис.
Теория. На практике каждый WSDL делается и затачивается практически для одного потребителя.
Опять теория. Для доступа к базе данных между разными вызовами WS отсутствует целостность (кто-нибудь пробовал поднять транзакционный WS? это адище!). Фактически в большинстве случаев компоновка продьюсер-консумер 1:1. Особенно, если вы используете очереди JMS.
Ложь и провокация! Придется оплачивать недешевый дополнительный уровень с комадной бестолковых бездельников. Использование SOA само по себе создает новые проблемы, которых бы не было без нее, начиная от постоянных ошибок в меппинге данных и кончая сложными отказами SOA-продуктов, за которыми приходится стучаться в службу поддержки. Один из недостатков SOA — огромные ресурсозатраты при сомнительном выигрыше. Решения SOA стоят занебесных цен (Oracle SOA Suite, IBM Websphere Business Integration). Специалисты по этим системам стоят дороже, нежели core-разработчики. Разработка, как ни странно, усложняется в разы, а тестирование систем — в десятки раз. Плюс, все решения глючные, неповоротливые и черезмерно навороченные.
Есть такой велосипед. Обычно добавляют хидеры для роутинга сообщения и адрес для callback. Однако всегда есть очень дорогой и понтовый «legacy систем», который будет класть на весь ваш SOA и протокол общения.
SOA не предназначена для фронтендов. Это корпоративная архитектура. Для фронтендов обычно делают легкий бекенд.
Вырезка из PowerPoint презентации… Что мешает делать это без SOA?
P.S. Да. Но чтобы добиться eventual consistency банки устраивали «учетный день» или даже «учетную неделю», когда денежные операции не принимались, или складывались в очередь, но не выполнялись. В современном мире невозможно, хотя в Европе большинство банков работают с клиентами до 14:00, а дальше, видимо, время eventual consistency :) Говорят, что и рождественские каникулы существуют для того, чтобы спокойно привести годовой баланс в порядок и закрыть бюджет :)
Дугой пример из того же банка. Сделали асинхронные транзакции, по одному актору на один счет. Все шустро и независимо работает: сообщения о денежных переводах роутятся к акторам, каждый актор смотрит баланс своего счета, и если он становится негативным, то операция запрещается. Теперь менеджер захотел, чтобы для определенных клиентов операция разрешалась, если суммарный баланс всех счетов одного клиента был бы неотрицательный, тогда как каждый счет в отдельности может стать негативным. Можно сделать новый тип акторов, который бы пропускал через себя все транзакции к счетам каждого клиента, но это потребует архитектурных изменений и затруднит совмесную работу со старыми акторами. Возвращаемся к блокировкам.
В CQRS моделях набор выполняемых операций ограничен, операции достаточно просты, чтобы быть выполненными внутри одного аггрегатора, а логика их выполнения не зависит от выполнения других операций или данных, лежащих вне аггрегатора. Для многих бизнес задач операция требует синхронного изменения сразу многих объектов. Выделить операции в атомарные акторы не представляется возможным, так как одни и те же данные используются разными операциями. Поэтому приходится искусственно создавать блокировки, либо использовать уже готовые средства, предоставляемые dbms.
Необходим конечный автомат с персистентным состоянием, который бы ловил эти события, коррелировал бы их в группы и применял бы какое-нибудь правило типа: если получили A, B, C (в любом порядке), то выкинуть X. Если получили A, C, D, то выкинуть Y, etc…
— невозможность свободной выборки данных из разных entity (то, что в ER делают JOIN-ы). Как выход предлагается денормализировать читаемые данные ввиде графа, чтобы выборка происходила по одной entity, которая включала бы в себя все необходимое. Но это далеко не всегда достижимо.
— отсутствие стабильного состояния в любой момент времени. То есть полное состояние — это то, что лежит в базе плюс все event-ы, разбросанные в данный момент по обработчикам. Как быть, если наш запрос — нечто более сложное, нежели атомарное изменение, а например: прочитать данные, проанализировать и в зависимости от этого выполнить ряд изменений разных сущностей? Без локов или транзакций состояние будет «уплывать», что ведет к потере целостности.
— большая сложность групповой обработки событий, например если нужно объединить несколько событий в одно большое. При декомпоновке событий очередность обработки не гарантируется.
Буду рад, если поправите.
Как я уже сказал, IoC — с точки зрения архитектуры концепт достаточно сомнительный. Организация архитектуры более традиционными способами имеет преимущества. Если все же нужен IoC, возьмите Guice — это мелкий IoC контейнер с отличной конфигурацией через DSL. Затем, в реальном проекте очень быстро понимаешь, что и в нем нет необходимости: связать пару бинов можно и ручками.
Затем идет пресловутый Spring MVC. Благодаря Struts и куче выросших на нем кодописунов, он быстро проник в массы и стал стандартом де-факто. Идея была понятна, но совершенно непроработана. В итоге, использование MVC на порядки усложнило разработку. Кроме того, MVC легко позволяет делать плохо, что в неумелых руках ведет к полной дезорганизации. Могу сказать, что в 90% сайтов хватит Servlet + JSP + JSTL, про которые все как-то очень быстро забыли.
Для справки, Apache Tapestry — гораздо более проработанный и организованный фреймворк, нежели Spring MVC.
Spring Data — совершенно рудиментарен. Запросы к базе данных в реальном приложении — это нечто более, чем найти объект в таблице по полям. Repository-per-entity — совершенно глупый концепт, который заставляет пользователя делать JOIN вручную, увеличивая в N раз число запросов к БД.
Spring Hateoas — искусственный концепт экспортировать Spring Data через Rest. Те же проблемы, только еще с участием HTTP.
Т.о. большинство концептов Spring-а искусственны и годны только в теории и для демок.
> Не совсем понял, что означает «заточка веб тольк очерез mvc»
Я об IoC и его scopes. В spring определен request scope, который сохраняет состояние объектов во время запроса. Однако его использование ограничено исключительно MVC контейнером. Когда запросы приходят, например, через JMS или RMI, придется разрабатывать и интегрировать свой scope. Сделать абстрактный request scope для всех endpoint-ов разработчики не догадались. Кроме того, MVC включено в Core Spring Framework, а не существует как отдельный проект, что вместе с другими технологиями делает понятным целевое использование фреймворка.
Вообще согласен, спорить особо не о чем. Spring предлагает большой инструментарий для разработки и интеграции. При хорошем владении и умелом использовании можно достичь хорошего качества продукта.
Насчет неплохого решения с архитектурной точки зрения я мог бы долго спорить. Тут и проблемы изначальной заточки только под разработку веб через MVC, и сомнительность самой парадигмы IoC, и куча совершенно ненужных прослоек, типа Spring Data, и тупо неудобство многих вещей, etc…
P.S. апофеоз «рамочной» инженерной мысли: Spring Batch с документацией в 250 страниц о том как написать скрипт для загрузки файла в базу данных.
Как правило фреймворк не имеет технической возможности не дать пользователю использовать «некорректные» для этого фреймворка конструкции. Поэтому фреймворк устанавливает ряд соглашений или спецификаций: типа так можно делать, а так нельзя. Спецификации всегда неполны и оставляют за кадром львиную долю неясностей и неопределенностей. Поэтому поведение большинства фреймворков описательно недетерминированно: в отличие от языка программирования, пользуясь только спецификацией, нельзя определить конечное поведение программы. Чтобы что-то написать, приходится изучать все тонкости внутренней имплементации фреймворка.
Каждый фреймворк предлагает набор шаблонов как «правильно» будет сделать ту или иную задачу. Типичные задачи решаются просто при помощи «магии»: декларативных конструкций фреймворка. Если фреймворк где-то не покрывает требуемый функционал, то найти обходное решение будет весьма сложным делом. Я видел проект, где функционал на 50% состоит из всевозможных подпорок и work-around-ов.
Фреймворки очень плохо интегрируются друг с другом, если эта интеграция не предусмотрена функционалом. В отличие от библиотек, каждая из которых ориентирована на конкретную задачу, и которые без проблем могут использоваться вместе в одном проекте, фреймворки вынуждены пухнуть и вмещать в себя сразу весь требуемый функционал.
Во-первых, задолбает писать для каждого поля safe-геттер. IDE такое не предлагает.
Во-вторых, если наша задача — минимизировать возможность ошибки, то здесь остается такая дверь ввиде традиционного геттера. Если я сделаю такой бин, угадайте какой из двух геттеров в 90% случаев будет использовать Вася?
Во-третьих, никто так не делает. Ни одна спецификация, рекомендация, или просто use-case не содержит подобного. Все библиотеки, фреймворки, работающие со свойствами, будут использовать традиционные аксессоры.
И, наконец, есть решение лучше и проще:
Проблема nullable не решается Optional, потому что его целью является ссылочный тип который изначально nullable по определению. Лишь дополнительно напрягает программиста распаковкой-запаковкой значений, и проверками, в которых как правило в 90% случаев нет необходимости.
Решение проблемы заключается в обратном: указать, что значение как раз не является nullable и оно safe для операций. Если идти путем Optional, то потребуется монада Some, и всего лишь переписать весь код, заменяя все non-nullable свободные ссылочные типы T на Some. Делов-то! Либо использовать другой способ, чтобы научить компилятор отличать non-nullable значения от всех остальных, например при помощи аннотаций. Или ввести в язык value-types.
P.S. Для любителей геморроя с Optional:
Равно как и значение функции, возвращающей non-null значения, может быть присвоено nullable переменной. Плюс функция, возвращающая String? ковариантна к String, т.е. при переопределении можно String? заменить на String:
Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc. В итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти. Так что я считаю Optional неудачной попыткой исправить nullable косяк при помощи системы типов, нежели изменениями в языке/компиляторе.
Во-первых, он не предназначен для хранения данных. Optional не имлементирует Serializable, что делает невозможным использование в remote-интерфейсах, и persistence объектах. Поля объекта не рекомендуется делать Optionak. Также не рекомендуется использовать Optional в списках. Насчет аксессоров нет полной определенности, Java Beans спецификация обходит этот вопрос. До сих пор не утихают споры по поводу использования Optional. Вот хороший пост на этот счет: stackoverflow.com/questions/24547673/why-java-util-optional-is-not-serializable-how-to-serialize-the-object-with-suc
Во-вторых, основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer, как например String? в Kotlin, что делает несовместимой всю семантику операций. Чтобы вставить поддержку Optional нужно перелопатить весь код ради сомнительной выгоды. В случае библиотеки, нужно выпускать радикально новую версию API.
В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.
В-четвертых. Практически нет библиотек или API, которые использовли бы Optional. Даже сам Java RT API.
Optional — это чисто ФП фича, который пришел в Java вместе с лямбдами и прочей ФП-лабудой, и в традиционном контексте не имеет большого смысла. Есть более успешная попытка искоренить null в Java при помощи аннотаций @Nonnull/@Nullable, ее и нужно придерживаться. Что реально может решить проблему, так это value types, но это пока еще proposals.
Как уже заметили, Spring Boot предлагает уже готовые связки с авто конфигурацией контекста, хотя усложняет в 10 раз что-либо изменить или добавить. Если же нужно в продакшне деплоить на сервер, то лучше использовать именно Stand-alone Jetty+Spring.
Scala изначально позиционировался как универсальный язык для всего. Основными целями было:
1. Создать гибкий инструмент, который бы позволил использовать вместе различные парадигмы.
2. Совместимость с Java.
3. Максимальная лаконичность записи. Ничего лишнего.
4. Создание новых синтаксических конструкций через DSL.
5. Наиболее полная система типов.
6. Стать новым IT стандартом.
Считалось, что эти ништяки станут привлекательным для большинства программистов и заставят их пересесть на новый велосипед. Плюс всевозможная реклама подстегивала интерес. Что получилось реально:
1. Различные парадигмы плохо сочетаются друг с другом. Кроме того, использовать в коде сразу несколько парадигм делает код запутанным, а сам язык увеличивает свой «порог вхождения». В 95% случаев функциональный подход используют для лямбд и работе с коллекциями: это модная фича, а программеры уже приучены. Функциональный подход при проектировании приложения встречается крайне редко. В итоге язык становится неким монстром Франкенштейна из плохо сшитых частей разных тел.
2. С дополнительными костылями. Коллекции несовместимы, структуры другие. Постоянно нужно держать в уме во что это будет компилиться. Учитывая что 99% библиотек и фреймворков написаны на Java, это дополнительная сложность.
3. Вообще странно. С одной стороны в Scala есть длинные ключевые слова, которые можно было бы сократить, с другой стороны, вся стардартная библиотека изобилует злоупотреблениями ввиде операторов. Вместо английского названия метода, объясняющего операцию, стоит одна или несколько закорючек, интуитивно не понятных без документации. В угоду DSL был окончательно испорчен синтаксис, позволяющий записывать одно и то же разными способами. Многие называют это гибкостью, когда это больше похоже на отсутствие костей. Сомнительная идея выражений через вызовы методов порождают сложные правила применения операций. В итоге, чем короче хочется написать, тем больше приходится нагружать язык синтаксической магией. Многие конструкции, типа XML, совершенно рудиментарные и искусственно внесены в язык, чтобы где-то сократить написание.
4. Удобство DSL — спорная идея, т.к. привносит магию. Обычно DSL используются для создания builder-а для сложного объекта по типу конфигурации. Попытка расширять конструкции языка через DSL сильно искусственна. Поэтому создатели Scala в последнее время смотрят в сторону макросов Nemerle.
5. А реальный смысл делать систему типов 100% type-safe и настолько сложной? Обычно в редких случаях, где появляется unchecked type casting, программист сам неплохо контролирует это. Как-то же программируют и на языках с динамической типизацией.
6. Несмотря на рекламу, Scala слабо адаптируется в среде программистов. Каждый обязан ее попробовать, но очень скоро вау-эффект пройдет. Создатели Scala ориентированы на совершенно других задачах, нежели требуется в реальных проектах. Скоро это поняли и создали инициативу typesafe.com. Однако покрываемая typesafe.com предметная область сильно ограничена. То есть, либо ты должен использовать Java фреймворк, что только усложняет ситуацию, либо шлепать функционал самому.
P.S. Если цитировать гения «Make it as simple as possible but not simpler», то Scala не может считаться гениальным языком :)
В том-то и дело, что не отдают. В JPA нет хорошей возможности сказать что конкретно и как доставать. До JPA 2.1 не было даже стандартного способа указать, какие атрибуты мне нужны, а какие нет. Были vendor-specific query hints, и работали через пень-колоду. А основная N+1 проблема до сих пор не имеет решения: JOIN FETCH присоединяет только одну коллекцию. В EclipseLink есть batch hint, который внезапно не работает для ManyToOne и OneToOne.
Видимо, разработчики JPA надеялись, что у нас будет один большой кеш, где будет лежать 80% данных всей базы, с объектами которого будет работать приложение, потихоньку подгружая недостающие части. Однако, как показывает практика, любая страничка с простой таблицей начисто рушит данный подход.
Мы делаем так: при вызове сервиса сначала вытаскивается все, что нужно и только то, что нужно одним или несколькими запросами. Для избавления N+1 запросов можно использовать join fetch или batch-fetch. В JPA 2.1 добавили EntityGraphs позволяющие более просто указывать relations, которые надо вытащить при запросе. Плюс есть нестандартные load-groups и fetch-groups. Если поле lazy и не проинициализировано, оно не должно использоваться. Затем service interceptor прогоняет граф через специальный фильтр, который пробегает все поля, обнуляя непроинициализированные прокси и заменяя проинициализированные коллекции на ArrayList и LinkedHashMap. На выходе получается полностью портабельный detached граф объектов. Почему EntityManager.detach() не делает то же самое — для меня загадка.