Panduan untuk Layanan Mikro Reaktif Menggunakan Kerangka Lagom

1. Ikhtisar

Dalam artikel ini, kita akan menjelajahi kerangka Lagom dan mengimplementasikan aplikasi contoh menggunakan arsitektur berbasis layanan mikro reaktif.

Sederhananya, aplikasi perangkat lunak reaktif mengandalkan komunikasi asynchronous yang digerakkan oleh pesan dan sangat Responsif , Tangguh , dan Elastis .

Dengan arsitektur berbasis layanan mikro, yang kami maksud adalah membagi sistem menjadi batas-batas antara layanan kolaboratif untuk mencapai tujuan Isolation , Autonomy , Single Responsibility , Mobility , dll. Untuk membaca lebih lanjut tentang kedua konsep ini, lihat Arsitektur Manifesto Reaktif dan Layanan Mikro Reaktif.

2. Mengapa Lagom?

Lagom adalah kerangka kerja sumber terbuka yang dibangun dengan peralihan dari monolit ke arsitektur aplikasi yang digerakkan oleh layanan mikro. Ini mengabstraksi kerumitan dalam membangun, menjalankan, dan memantau aplikasi yang didorong oleh layanan mikro.

Di balik layar, kerangka kerja Lagom menggunakan Kerangka Play, runtime berbasis pesan Akka, Kafka untuk layanan decoupling, Sumber Peristiwa, dan pola CQRS, dan dukungan ConductR untuk memantau dan menskalakan layanan mikro di lingkungan kontainer.

3. Halo Dunia di Lagom

Kami akan membuat aplikasi Lagom untuk menangani permintaan salam dari pengguna dan membalas dengan pesan ucapan bersama dengan statistik cuaca untuk hari itu.

Dan kami akan mengembangkan dua layanan mikro terpisah: Salam dan Cuaca.

Salam akan fokus pada penanganan permintaan salam, berinteraksi dengan layanan cuaca untuk membalas kembali kepada pengguna. Layanan mikro Cuaca akan melayani permintaan statistik cuaca untuk hari ini.

Dalam kasus pengguna yang sudah ada yang berinteraksi dengan layanan mikro Salam , pesan ucapan yang berbeda akan ditampilkan kepada pengguna.

3.1. Prasyarat

  1. Instal Scala (saat ini kami menggunakan versi 2.11.8) dari sini
  2. Instal alat build sbt (saat ini kami menggunakan 0.13.11) dari sini

4. Pengaturan Proyek

Sekarang mari kita lihat sekilas langkah-langkah untuk menyiapkan sistem Lagom yang berfungsi.

4.1. SBT Build

Buat folder proyek lagom-hello-world diikuti dengan file build build.sbt . Sistem Lagom biasanya terdiri dari sekumpulan build sbt dengan setiap build sesuai dengan grup layanan terkait:

organization in ThisBuild := "com.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))

Untuk memulai, kami telah menentukan detail organisasi, versi skala , dan menonaktifkan Kafka untuk proyek saat ini. Lagom mengikuti konvensi dua proyek terpisah untuk setiap layanan mikro : proyek API dan proyek implementasi.

Proyek API berisi antarmuka layanan yang menjadi tempat bergantung implementasi.

Kami telah menambahkan dependensi ke modul Lagom yang relevan seperti lagomJavadslApi , lagomJavadslPersistenceCassandra untuk menggunakan Lagom Java API di layanan mikro kami dan menyimpan acara yang terkait dengan entitas persisten di Cassandra, masing-masing.

Juga, proyek ucapan-impl bergantung pada proyek cuaca-api untuk mengambil dan menyajikan statistik cuaca sambil menyapa pengguna.

Dukungan untuk plugin Lagom ditambahkan dengan membuat folder plugin dengan file plugins.sbt , memiliki entri untuk plugin Lagom. Ini menyediakan semua dukungan yang diperlukan untuk membangun, menjalankan, dan menerapkan aplikasi kita.

Selain itu, plugin sbteclipse akan berguna jika kita menggunakan Eclipse IDE untuk proyek ini. Kode di bawah ini menunjukkan konten untuk kedua plugin:

addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")

Buat file project / build.properties dan tentukan versi sbt yang akan digunakan:

sbt.version=0.13.11

4.2. Pembuatan Proyek

Menjalankan perintah sbt dari root proyek akan menghasilkan template proyek berikut:

  1. salam-api
  2. salam-impl
  3. cuaca-api
  4. cuaca-impl

Sebelum kita mulai mengimplementasikan microservices, mari tambahkan folder src / main / java dan src / main / java / resources di dalam setiap proyek, untuk mengikuti tata letak direktori proyek seperti Maven.

Selain itu, dua proyek dinamis dibuat di dalam project-root / target / lagom-dynamic-projects :

  1. lagom-internal-meta-proyek-cassandra
  2. lagom-internal-meta-project-service-locator

Proyek-proyek ini digunakan secara internal oleh Lagom.

5. Antarmuka Layanan

Dalam proyek api-salam , kami menetapkan antarmuka berikut:

public interface GreetingService extends Service { public ServiceCall handleGreetFrom(String user); @Override default Descriptor descriptor() { return named("greetingservice") .withCalls(restCall(Method.GET, "/api/greeting/:fromUser", this::handleGreetFrom)) .withAutoAcl(true); } }

GreetingService exposes handleGreetFrom() to handle greet request from the user. A ServiceCall API is used as the return type of these methods. ServiceCall takes two type parameters Request and Response.

The Request parameter is the type of the incoming request message, and the Response parameter is the type of the outgoing response message.

In the example above, we're not using request payload, request type is NotUsed, and Response type is a String greeting message.

GreetingService also specifies a mapping to the actual transport used during the invocation, by providing a default implementation of the Service.descriptor() method. A service named greetingservice is returned.

handleGreetFrom() service call is mapped using a Rest identifier: GET method type and path identifier /api/greeting/:fromUser mapped to handleGreetFrom() method. Check this link out for more details on service identifiers.

On the same lines, we define WeatherService interface in the weather-api project. weatherStatsForToday() method and descriptor() method are pretty much self explanatory:

public interface WeatherService extends Service { public ServiceCall weatherStatsForToday(); @Override default Descriptor descriptor() { return named("weatherservice") .withCalls( restCall(Method.GET, "/api/weather", this::weatherStatsForToday)) .withAutoAcl(true); } };

WeatherStats is defined as an enum with sample values for different weather and random lookup to return weather forecast for the day:

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

Simply put, in a system making use of Event Sourcing, we'll be able to capture all changes as immutable domain events appended one after the other. The current state is derived by replaying and processing events. This operation is essentially a foldLeft operation known from the Functional Programming paradigm.

Event sourcing helps to achieve high write performance by appending the events and avoiding updates and deletes of existing events.

Let's now look at our persistent entity in the greeting-impl project, GreetingEntity:

public class GreetingEntity extends PersistentEntity { @Override public Behavior initialBehavior( Optional 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 provides PersistentEntity API for processing incoming events of type Command via setCommandHandler() methods and persist state changes as events of type Event. The domain object state is updated by applying the event to the current state using the setEventHandler() method. The initialBehavior() abstract method defines the Behavior of the entity.

In initialBehavior(), we build original GreetingState “Hello” text. Then we can define a ReceivedGreetingCommand command handler – which produces a ReceivedGreetingEvent Event and gets persisted in the event log.

GreetingState is recalculated to “Hello Again” by the ReceivedGreetingEvent event handler method. As mentioned earlier, we're not invoking setters – instead, we are creating a new instance of State from the current event being processed.

Lagom follows the convention of GreetingCommand and GreetingEvent interfaces for holding together all the supported commands and events:

public interface GreetingCommand extends Jsonable { @JsonDeserialize public class ReceivedGreetingCommand implements GreetingCommand, CompressedJsonable, PersistentEntity.ReplyType { @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. Service Implementation

7.1. Greeting Service

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 handleGreetFrom(String user) { return request -> { PersistentEntityRef ref = persistentEntityRegistry.refFor( GreetingEntity.class, user); CompletableFuture greetingResponse = ref.ask(new ReceivedGreetingCommand(user)) .toCompletableFuture(); CompletableFuture todaysWeatherInfo = (CompletableFuture) 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"); } }; } }

Simply put, we inject the PersistentEntityRegistry and WeatherService dependencies using @Inject (provided by Guice framework), and we register the persistent GreetingEntity.

The handleGreetFrom() implementation is sending ReceivedGreetingCommand to the GreetingEntity to process and return greeting string asynchronously using CompletableFuture implementation of CompletionStage API.

Similarly, we make an async call to Weather microservice to fetch weather stats for today.

Finally, we concatenate both outputs and return the final result to the user.

To register an implementation of the service descriptor interface GreetingService with Lagom, let's create GreetingServiceModule class which extends AbstractModule and implements ServiceGuiceSupport:

public class GreetingServiceModule extends AbstractModule implements ServiceGuiceSupport { @Override protected void configure() { bindServices( serviceBinding(GreetingService.class, GreetingServiceImpl.class)); bindClient(WeatherService.class); } } 

Also, Lagom internally uses the Play Framework. And so, we can add our module to Play's list of enabled modules in src/main/resources/application.conf file:

play.modules.enabled += com.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule

7.2. Weather Service

After looking at the GreetingServiceImpl, WeatherServiceImpl is pretty much straightforward and self-explanatory:

public class WeatherServiceImpl implements WeatherService { @Override public ServiceCall weatherStatsForToday() { return req -> CompletableFuture.completedFuture(WeatherStats.forToday()); } }

We follow the same steps as we did above for greeting module to register the weather module with Lagom:

public class WeatherServiceModule extends AbstractModule implements ServiceGuiceSupport { @Override protected void configure() { bindServices(serviceBinding( WeatherService.class, WeatherServiceImpl.class)); } }

Also, register the weather module to Play's framework list of enabled modules:

play.modules.enabled += com.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule

8. Running the Project

Lagom allows running any number of services together with a single command.

We can start our project by hitting the below command:

sbt lagom:runAll

This will start the embedded Service Locator, embedded Cassandra and then start microservices in parallel. The same command also reloads our individual microservice when the code changes so that we don’t have to restart them manually.

We can be focused on our logic and Lagom handle the compilation and reloading. Once started successfully, we will see the following output:

................ [info] Cassandra server running at 127.0.0.1:4000 [info] Service locator is running at //localhost:8000 [info] Service gateway is running at //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...)

Once started successfully we can make a curl request for greeting:

curl //localhost:9000/api/greeting/Amit

We will see following output on the console:

Hello Amit! Today's weather stats: Going to Rain, Take Umbrella

Menjalankan permintaan curl yang sama untuk pengguna yang sudah ada akan mengubah pesan salam:

Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella

9. Kesimpulan

Pada artikel ini, kami telah membahas cara menggunakan kerangka kerja Lagom untuk membuat dua layanan mikro yang berinteraksi secara asinkron.

Kode sumber lengkap dan semua cuplikan kode untuk artikel ini tersedia di proyek GitHub.