1. Обзор
В этой статье мы рассмотрим платформу Lagom и реализуем пример приложения с использованием реактивной архитектуры, управляемой микросервисами.
Проще говоря, реактивные программные приложения полагаются на асинхронную связь, управляемую сообщениями, и по своей природе очень отзывчивы
, устойчивы
и эластичны
.
Под архитектурой, управляемой микросервисами, мы подразумевали разделение системы на границы между совместными сервисами для достижения таких целей , как изоляция
, автономия
, единая ответственность
, мобильность
и т. д . Дополнительные сведения об этих двух концепциях см . в The Reactive Manifesto и Reactive Microservices Architecture .
2. Почему Лагом?
Lagom — это платформа с открытым исходным кодом, созданная с учетом перехода от монолитов к архитектуре приложений, основанной на микросервисах. Он абстрагируется от сложности создания, запуска и мониторинга приложений, управляемых микросервисами.
За кулисами Lagom framework использует Play Framework , управляемую сообщениями среду выполнения Akka , Kafka для разделения сервисов, шаблоны Event Sourcing и CQRS , а также поддержку ConductR для мониторинга и масштабирования микросервисов в контейнерной среде.
3. Hello World в Лагоме
Мы создадим приложение Lagom для обработки приветственного запроса от пользователя и ответа приветственным сообщением вместе со статистикой погоды на день.
И мы будем разрабатывать два отдельных микросервиса: Приветствие
и Погода.
Приветствие
будет сосредоточено на обработке запроса на приветствие, взаимодействии с погодной службой для ответа пользователю. Микросервис Weather
будет обслуживать запрос статистики погоды на сегодня.
В случае, если существующий пользователь взаимодействует с микрослужбой
приветствия, пользователю будет показано другое приветственное сообщение.
3.1. Предпосылки
- Установите
Scala
(сейчас мы используем версию 2.11.8) отсюда - Установите инструмент сборки
sbt
(в настоящее время мы используем 0.13.11) отсюда
4. Настройка проекта
Давайте теперь кратко рассмотрим шаги по настройке работающей системы Lagom.
4.1. Сборка СБТ
Создайте папку проекта lagom-hello-world
, а затем файл сборки build.sbt
. Система Lagom обычно состоит из набора сборок sbt,
каждая из которых соответствует группе связанных сервисов: `` ``
organization in ThisBuild := "com.foreach"
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
, для использования Lagom Java API в наших микросервисах и хранения событий, связанных с постоянной сущностью в Cassandra,
соответственно.
Кроме того, проект Greeting-Imple
зависит от проекта 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
из корня проекта создаст следующие шаблоны проекта:
- приветствие API
- приветствие-импл
- погода-api
- погода-импл
Прежде чем мы начнем реализовывать микросервисы, давайте добавим папки src/main/java
и src/main/java/resources
в каждый из проектов, чтобы следовать макету каталога проекта, подобному Maven.
Кроме того, внутри project-root/target/lagom-dynamic-projects
генерируются два динамических проекта :
- Лагом-внутренний мета-проект-кассандра
- lagom-internal-meta-project-service-locator
Эти проекты используются 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()
для обработки запроса приветствия от пользователя. ServiceCall API
используется в качестве возвращаемого типа этих методов. ServiceCall
принимает два параметра типа Request
и Response
.
Параметр Request
— это тип входящего сообщения запроса, а параметр Response
— тип исходящего ответного сообщения.
В приведенном выше примере мы не используем полезные данные запроса, тип запроса — NotUsed
, а тип ответа
— приветственное сообщение String .
GreetingService
также указывает сопоставление с фактическим транспортом, используемым во время вызова, предоставляя реализацию метода Service.descriptor()
по умолчанию . Возвращается служба с именем GreetingService
.
Вызов службы handleGreetFrom()
сопоставляется с использованием идентификатора Rest: тип метода GET и идентификатор пути
/api/greeting/:fromUser
сопоставляются с методом handleGreetFrom()
. Перейдите по этой ссылке для получения более подробной информации об идентификаторах служб.
Точно так же мы определяем интерфейс 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
, мы сможем фиксировать все изменения в виде неизменяемых доменных событий, добавляемых одно за другим. Текущее состояние определяется путем воспроизведения и обработки событий. Эта операция по существу является операцией foldLeft
, известной из парадигмы функционального программирования.
Источник событий помогает достичь высокой производительности записи, добавляя события и избегая обновлений и удалений существующих событий.
Давайте теперь посмотрим на нашу постоянную сущность в проекте Greeting-impl, 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 предоставляет API PersistentEntity<Command, Entity, Event>
для обработки входящих событий типа Command
с помощью методов setCommandHandler()
и сохранения изменений состояния как событий типа Event
. Состояние объекта домена обновляется путем применения события к текущему состоянию с помощью метода setEventHandler()
. Абстрактный метод initialBehavior() определяет
поведение
объекта.
В 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
реализации CompletionStage
API.
Точно так же мы делаем асинхронный вызов микрослужбы погоды
, чтобы получить статистику погоды на сегодня.
Наконец, мы объединяем оба выхода и возвращаем окончательный результат пользователю.
Чтобы зарегистрировать реализацию интерфейса дескриптора службы 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
+= com.foreach.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:
play.modules.enabled
+= com.foreach.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 для приветствия:
curl http://localhost:9000/api/greeting/Amit
В консоли мы увидим следующий вывод:
Hello Amit! Today's weather stats: Going to Rain, Take Umbrella
Выполнение того же запроса curl для существующего пользователя изменит приветственное сообщение:
Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella
9. Заключение
В этой статье мы рассмотрели, как использовать платформу Lagom для создания двух микросервисов, взаимодействующих асинхронно.
Полный исходный код и все фрагменты кода для этой статьи доступны в проекте GitHub .