Руководство по реактивным микросервисам с использованием Lagom Framework

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 , пользователю будет показано другое приветственное сообщение.

3.1. Предпосылки

, Установите Scala (в настоящее время мы используем версию 2.11.8) из

here , Установите инструмент сборки sbt (в настоящее время мы используем 0.13.11) из

** 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 .