1. Обзор
В этой статье мы рассмотрим структуру Lagom и реализуем пример приложения с использованием архитектуры, управляемой реактивными микросервисами.
Проще говоря, реагирующие программные приложения основаны на асинхронной связи, управляемой сообщениями, и по своей природе очень Responsive , Resilient и Elastic .
Под архитектурой, управляемой микросервисами, мы подразумевали разделение системы на границы между сервисами для совместной работы для достижения целей Isolation , Autonomy , Single Responsibility , Mobility и т. Д. Для получения дополнительной информации об этих двух концепциях см. Http://www.reactivemanifesto.org/[Реактивный манифест]и Reactive Microservices Architecture .
2. Почему Лагом?
Lagom - это платформа с открытым исходным кодом, созданная с учетом перехода от монолитов к архитектуре приложений на основе микросервисов. Он описывает сложность создания, запуска и мониторинга приложений на основе микросервисов.
За кулисами Lagom Framework использует Play Framework , Akka управляемую сообщениями среду выполнения, Kafka для развязки сервисов, шаблоны Event Sourcing и CQRS , а также https://поведения .lightbend.com/[ConductR]поддержка для мониторинга и масштабирования микросервисов в контейнерной среде.
** 3. Привет, мир в Лагоме
**
Мы создадим приложение Lagom для обработки приветственного запроса от пользователя и ответного с приветственным сообщением вместе со статистикой погоды за день.
И мы будем разрабатывать два отдельных микросервиса: Greeting и Weather.
Greeting будет фокусироваться на обработке приветствия, взаимодействуя с метеослужбой, чтобы ответить пользователю. Микросервис Weather будет обслуживать запрос статистики погоды на сегодня.
В случае существующего пользователя, взаимодействующего с микросервисом Greeting , пользователю будет показано другое приветственное сообщение.
** 4. Настройка проекта
**
Теперь давайте кратко рассмотрим шаги по настройке работающей системы Lagom.
4.1. SBT Build
Создайте папку проекта lagom-hello-world с последующим файлом сборки build.sbt . Система Lagom обычно состоит из набора sbt сборок, каждая из которых соответствует группе связанных сервисов:
organization in ThisBuild := "org.baeldung"
scalaVersion in ThisBuild := "2.11.8"
lagomKafkaEnabled in ThisBuild := false
lazy val greetingApi = project("greeting-api")
.settings(
version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq(
lagomJavadslApi
)
)
lazy val greetingImpl = project("greeting-impl")
.enablePlugins(LagomJava)
.settings(
version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq(
lagomJavadslPersistenceCassandra
)
)
.dependsOn(greetingApi, weatherApi)
lazy val weatherApi = project("weather-api")
.settings(
version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq(
lagomJavadslApi
)
)
lazy val weatherImpl = project("weather-impl")
.enablePlugins(LagomJava)
.settings(
version := "1.0-SNAPSHOT"
)
.dependsOn(weatherApi)
def project(id: String) = Project(id, base = file(id))
Для начала мы указали сведения об организации, scala версию и отключили Kafka для текущего проекта. Lagom придерживается соглашения о двух отдельных проектах для каждого микросервиса : проект API и проект внедрения.
Проект API содержит сервисный интерфейс, от которого зависит реализация.
Мы добавили зависимости в соответствующие модули Lagom, такие как lagomJavadslApi , lagomJavadslPersistenceCassandra , для использования Java-API Lagom в наших микросервисах и хранения событий, связанных с персистентной сущностью в Cassandra, соответственно.
Кроме того, проект greeting-impl зависит от проекта weather-api для получения и предоставления статистики погоды при приветствии пользователя.
Поддержка плагина Lagom добавляется путем создания папки плагина с файлом plugins.sbt , имеющей запись для плагина Lagom. Он обеспечивает всю необходимую поддержку для создания, запуска и развертывания нашего приложения.
Кроме того, плагин sbteclipse будет полезен, если мы будем использовать Eclipse IDE для этого проекта. Код ниже показывает содержимое обоих плагинов:
addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")
Создайте файл project/build.properties и укажите sbt версию для использования:
sbt.version=0.13.11
** 4.2. Генерация проекта
**
Выполнение команды sbt из корня проекта сгенерирует следующие шаблоны проекта:
, приветствие-апи
, приветствие-реализ
, погода-апи
, погода-реализ
Прежде чем мы начнем реализовывать микросервисы, давайте добавим папки src/main/java и src/main/java/resources внутри каждого из проектов, чтобы следовать макету директории проекта, подобному Maven.
Кроме того, два динамических проекта создаются внутри project-root/target/lagom-dynamic-projects :
, инжекционный-внутренний мета-проект-Cassandra
, инжекционный-внутренний мета-проект-сервис-локатор
Эти проекты используются внутри компании Lagom.
** 5. Сервисный интерфейс
**
В проекте greeting-api мы указываем следующий интерфейс:
public interface GreetingService extends Service {
public ServiceCall<NotUsed, String> handleGreetFrom(String user);
@Override
default Descriptor descriptor() {
return named("greetingservice")
.withCalls(restCall(Method.GET, "/api/greeting/:fromUser",
this::handleGreetFrom))
.withAutoAcl(true);
}
}
GreetingService предоставляет handleGreetFrom () для обработки приветствия от пользователя. В качестве типа возврата этих методов используется API ServiceCall . ServiceCall принимает два параметра типа Request и Response .
Параметр Request является типом сообщения входящего запроса, а параметр Response является типом сообщения исходящего ответа.
В приведенном выше примере мы не используем полезную нагрузку запроса, тип запроса - NotUsed , а тип Response - это String приветственное сообщение.
GreetingService также указывает отображение на фактический транспорт, используемый во время вызова, предоставляя реализацию по умолчанию метода Service.descriptor () . Служба с именем greetingservice возвращается.
handleGreetFrom () вызов службы отображается с помощью идентификатора Rest:
GET тип метода и идентификатор пути /api/приветствие/: fromUser сопоставлены с методом handleGreetFrom () . Проверьте this link для получения более подробной информации об идентификаторах услуг.
В тех же строках мы определяем интерфейс WeatherService в проекте weather-api . Метод weatherStatsForToday () и метод descriptor () говорят сами за себя:
public interface WeatherService extends Service {
public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday();
@Override
default Descriptor descriptor() {
return named("weatherservice")
.withCalls(
restCall(Method.GET, "/api/weather",
this::weatherStatsForToday))
.withAutoAcl(true);
}
};
WeatherStats определяется как перечисление с выборочными значениями для различной погоды и случайным поиском для возврата прогноза погоды на день:
public enum WeatherStats {
STATS__RAINY("Going to Rain, Take Umbrella"),
STATS__HUMID("Going to be very humid, Take Water");
public static WeatherStats forToday() {
return VALUES.get(RANDOM.nextInt(SIZE));
}
}
6. Lagom Persistence - Event Sourcing
Проще говоря, в системе, использующей Event Sourcing , мы сможем фиксировать все изменения как события неизменного домена, добавляемые одно за другим. Текущее состояние определяется путем воспроизведения и обработки событий. Эта операция по сути является операцией foldLeft , известной из парадигмы функционального программирования.
Источник событий помогает достичь высокой производительности записи, добавляя события и избегая обновлений и удалений существующих событий.
Давайте теперь рассмотрим нашу постоянную сущность в проекте приветствия, GreetingEntity
public class GreetingEntity extends
PersistentEntity<GreetingCommand, GreetingEvent, GreetingState> {
@Override
public Behavior initialBehavior(
Optional<GreetingState> snapshotState) {
BehaviorBuilder b
= newBehaviorBuilder(new GreetingState("Hello "));
b.setCommandHandler(
ReceivedGreetingCommand.class,
(cmd, ctx) -> {
String fromUser = cmd.getFromUser();
String currentGreeting = state().getMessage();
return ctx.thenPersist(
new ReceivedGreetingEvent(fromUser),
evt -> ctx.reply(
currentGreeting + fromUser + "!"));
});
b.setEventHandler(
ReceivedGreetingEvent.class,
evt -> state().withMessage("Hello Again "));
return b.build();
}
}
Lagom предоставляет PersistentEntity <Command, Entity, Event> API для обработки входящих события типа Command с помощью методов setCommandHandler () и сохраняют изменения состояния как события типа Event . Состояние объекта домена обновляется путем применения события к текущему состоянию с помощью метода setEventHandler () . The initialBehavior () абстрактный метод определяет Behavior объекта.
В initialBehavior () мы создаем оригинальный текст GreetingState «Hello».
Затем мы можем определить обработчик команды ReceivedGreetingCommand , который создает событие ReceivedGreetingEvent и сохраняется в журнале событий.
GreetingState пересчитывается в «Hello Again» методом обработчика события ReceivedGreetingEvent . Как упоминалось ранее, мы не вызываем сеттеры - вместо этого мы создаем новый экземпляр State из текущего обрабатываемого события .
Lagom следует соглашению интерфейсов GreetingCommand и GreetingEvent для хранения всех поддерживаемых команд и событий:
public interface GreetingCommand extends Jsonable {
@JsonDeserialize
public class ReceivedGreetingCommand implements
GreetingCommand,
CompressedJsonable,
PersistentEntity.ReplyType<String> {
@JsonCreator
public ReceivedGreetingCommand(String fromUser) {
this.fromUser = Preconditions.checkNotNull(
fromUser, "fromUser");
}
}
}
public interface GreetingEvent extends Jsonable {
class ReceivedGreetingEvent implements GreetingEvent {
@JsonCreator
public ReceivedGreetingEvent(String fromUser) {
this.fromUser = fromUser;
}
}
}
** 7. Внедрение сервиса
**
7.1. Приветствие
public class GreetingServiceImpl implements GreetingService {
@Inject
public GreetingServiceImpl(
PersistentEntityRegistry persistentEntityRegistry,
WeatherService weatherService) {
this.persistentEntityRegistry = persistentEntityRegistry;
this.weatherService = weatherService;
persistentEntityRegistry.register(GreetingEntity.class);
}
@Override
public ServiceCall<NotUsed, String> handleGreetFrom(String user) {
return request -> {
PersistentEntityRef<GreetingCommand> ref
= persistentEntityRegistry.refFor(
GreetingEntity.class, user);
CompletableFuture<String> greetingResponse
= ref.ask(new ReceivedGreetingCommand(user))
.toCompletableFuture();
CompletableFuture<WeatherStats> todaysWeatherInfo
= (CompletableFuture<WeatherStats>) weatherService
.weatherStatsForToday().invoke();
try {
return CompletableFuture.completedFuture(
greetingResponse.get() + " Today's weather stats: "
+ todaysWeatherInfo.get().getMessage());
} catch (InterruptedException | ExecutionException e) {
return CompletableFuture.completedFuture(
"Sorry Some Error at our end, working on it");
}
};
}
}
Проще говоря, мы внедряем зависимости PersistentEntityRegistry и WeatherService , используя @ Inject (предоставляемый фреймворком Guice ), и регистрируем постоянное GreetingEntity .
Реализация handleGreetFrom () отправляет ReceivedGreetingCommand в GreetingEntity для асинхронной обработки и возврата строки приветствия с использованием реализации CompletableFuture API CompletionStage .
Точно так же мы делаем асинхронный вызов микросервиса Weather для получения статистики погоды за сегодня.
Наконец, мы объединяем оба вывода и возвращаем конечный результат пользователю.
Чтобы зарегистрировать реализацию интерфейса дескриптора службы GreetingService с Lagom, давайте создадим класс GreetingServiceModule , который расширяет AbstractModule и реализует ServiceGuiceSupport :
public class GreetingServiceModule extends AbstractModule
implements ServiceGuiceSupport {
@Override
protected void configure() {
bindServices(
serviceBinding(GreetingService.class, GreetingServiceImpl.class));
bindClient(WeatherService.class);
}
}
Также Lagom внутренне использует Play Framework. Итак, мы можем добавить наш модуль в список включенных модулей Play в файле src/main/resources/application.conf :
play.modules.enabled
+= org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule
7.2. Метеослужба
Посмотрев на GreetingServiceImpl , WeatherServiceImpl довольно прост и не требует пояснений:
public class WeatherServiceImpl implements WeatherService {
@Override
public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday() {
return req ->
CompletableFuture.completedFuture(WeatherStats.forToday());
}
}
Мы выполняем те же шаги, что и выше, для модуля приветствия, чтобы зарегистрировать модуль погоды в Lagom:
public class WeatherServiceModule
extends AbstractModule
implements ServiceGuiceSupport {
@Override
protected void configure() {
bindServices(serviceBinding(
WeatherService.class,
WeatherServiceImpl.class));
}
}
Также зарегистрируйте модуль погоды в списке включенных модулей Play Framework:
play.modules.enabled
+= org.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule
** 8. Запуск проекта
**
Lagom позволяет запускать любое количество сервисов одной командой .
Мы можем начать наш проект, нажав следующую команду:
sbt lagom:runAll
Это запустит встроенный Service Locator , встроенный Cassandra , а затем запустит микроуслуги параллельно. Эта же команда также перезагружает наш отдельный микросервис при изменении кода, так что нам не нужно перезапускать их вручную.
Мы можем сосредоточиться на нашей логике, и Lagom справится с компиляцией и перезагрузкой. После успешного запуска мы увидим следующий результат:
................[info]Cassandra server running at 127.0.0.1:4000[info]Service locator is running at http://localhost:8000[info]Service gateway is running at http://localhost:9000[info]Service weather-impl listening for HTTP on 0:0:0:0:0:0:0:0:56231 and how the services interact via[info]Service greeting-impl listening for HTTP on 0:0:0:0:0:0:0:0:49356[info](Services started, press enter to stop and go back to the console...)
После успешного запуска мы можем сделать запрос на приветствие:
curl http://localhost:9000/api/greeting/Amit
Мы увидим следующий вывод на консоли:
Hello Amit! Today's weather stats: Going to Rain, Take Umbrella
Выполнение того же запроса скручивания для существующего пользователя изменит приветствие:
Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella
** 9. Заключение
**
В этой статье мы рассмотрели, как использовать инфраструктуру Lagom для создания двух микро-сервисов, которые взаимодействуют асинхронно.
Полный исходный код и все фрагменты кода для этой статьи доступны по адресу in в проекте GitHub .