Skip to content

Commit

Permalink
Documents ServiceInfo being required to run a micro service in Lagom. (
Browse files Browse the repository at this point in the history
  • Loading branch information
ignasi35 authored and jroper committed Feb 20, 2017
1 parent 196b7d7 commit d9f4df0
Show file tree
Hide file tree
Showing 22 changed files with 227 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ lazy val p = (project in file("p")).enablePlugins(PlayJava && LagomPlay)
routesGenerator := InjectedRoutesGenerator,
libraryDependencies ++= Seq(lagomJavadslClient, lagomJavadslApi)
)
.dependsOn(`a-api`)

InputKey[Unit]("verifyReloadsProjA") := {
val expected = Def.spaceDelimited().parsed.head.toInt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.lightbend.lagom.javadsl.client.ServiceClientGuiceSupport;
import com.lightbend.lagom.javadsl.api.*;
import play.*;
import api.FooService;
import javax.inject.Inject;
import java.util.Date;
import java.io.*;
Expand All @@ -13,6 +14,8 @@ public class Module extends AbstractModule implements ServiceClientGuiceSupport
protected void configure() {
ServiceAcl pAcl = new ServiceAcl(Optional.empty(), Optional.of("/p"));
ServiceAcl assetsAcl = new ServiceAcl(Optional.empty(), Optional.of("/assets/.*"));

bindServiceInfo(ServiceInfo.of("p", pAcl, assetsAcl));
bindClient(FooService.class);
}
}
33 changes: 33 additions & 0 deletions docs/manual/common/concepts/ServiceDiscovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Service Discovery

Microservices must support location transparency: the ability to reside on multiple hosts for load balancing and resilience, to move from one host to another when failures occur, and to scale the number of hosts up and down as load changes.

If a microservice's instances keep moving, spawning and dying, a mechanism for the microservice client to locate an instance is required. A Service Registry maintains an up-to-date list of available microservices that includes the host and port for each instance of each microservice. As load changes, the system can spawn or destroy instances in any location and continue to satisfy requests because the Service Registry collaborates with the microservice's instances to maintain the lookup table updated.

In order to register every microservice in the Service Registry there are two alternatives: self-registration and [3rd Party Registration](http://microservices.io/patterns/3rd-party-registration.html). When booting a Lagom microservice instance a registrar will register the name of the microservice, the URL and the names of the locatable Service Descriptors on the Service Registry so that they can be located. When powering down an instance of a service, the registrar will have to update the Service Registry too. Lagom's [[Developer Environment|DevEnvironment]] provides an implementation of a Service Registry and a registrar so you can run your microservices locally.

There are plenty of technologies providing Service Registry capabilities. You will need to choose and/or develop a Service Locator for your services to run in your deployment environment (see for example [Lagom ZooKeeper Service Locator](https://github.com/jboner/lagom-service-locator-zookeeper)). You may need to work out a way to plug your Lagom services with a registrar. Lagom's integration with [[ConductR|ConductR]] makes this two steps seamless.


## Client-Side Service Discovery

From Bonér's [Reactive Microservices Architecture: Design Principles for Distributed Systems](http://www.oreilly.com/programming/free/reactive-microservices-architecture.html)

> Once the information about each service has been stored it can be made available through a Service Registry that services can use to look the information up—using a pattern called Client-Side Service Discovery.
Lagom creates service clients for each Service Descriptor so that applications can interact with Lagom Services. So [[an app|IntegratingNonLagom]] that wanted to consume the hello service can use the Welcome Service Client and simply invoke the `hello` method. The Welcome Service Client will use the Service Registry to find a valid URL where `welcome` is available and with that information the request is then fulfilled. This approach requires using Lagom provided code.

When running your code in production the Service Locator plugged into your services will be an element participating in this Client-Side Discovery.

## Server-Side Service Discovery

From Bonér's [Reactive Microservices Architecture: Design Principles for Distributed Systems](http://www.oreilly.com/programming/free/reactive-microservices-architecture.html)

> Another strategy is to have the information stored and maintained in a load balancer [...] using a pattern called Server-Side Service Discovery.
Lagom Developer Mode starts all your services plus a Service Registry and a Service Gateway. The Service Gateway allows Service Clients to consume the endpoints provided by the Service Descriptors registered. A browser can display a hello message to a user by requesting the `/hello/steve` path to the Service Gateway. The Service Gateway will know what service provide that and will request the Service Registry for an instance of that service. The Service Registry will respond with the host and port of the instance where the request can be fulfilled. Finally the Service Gateway will perform the request and return the result to the browser. In this model, the browser only needs to know where the Service Gateway is. When using server-side discovery, a call on a Service Descriptor can only be reached if its call is added to the ACL.

This section uses HTTP-REST for all the examples but the concepts apply to any kind of traffic be it HTTP, binary over tcp, etc...

Server-Side Service Discovery is very convenient when you can't embed your Service Locator on every client.

1 change: 1 addition & 0 deletions docs/manual/common/concepts/index.toc
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Technologies:Lagom component technologies
BuildConcepts:Lagom build philosophy
Immutable:Immutable Objects
ES_CQRS:Event Sourcing and CQRS
ServiceDiscovery: Service Discovery
11 changes: 9 additions & 2 deletions docs/manual/java/guide/services/ServiceClients.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ We've seen how to define service descriptors and how to implement them, now we n

## Binding a service client

The first thing necessary to consume a service is to bind it, so that Lagom can provide an implementation for your application to use. This can be done using the `bindClient` method on [ServiceClientGuiceSupport](api/index.html?com/lightbend/lagom/javadsl/client/ServiceClientGuiceSupport.html). If you're already binding a service implementation using [ServiceGuiceSupport](api/index.html?com/lightbend/lagom/javadsl/server/ServiceGuiceSupport.html), this interface extends `ServiceClientGuiceSupport`, so your existing module can be used as is:
The first thing necessary to consume a service is to bind it, so that Lagom can provide an implementation for your application to use. This can be done using the `bindClient` method on [ServiceClientGuiceSupport](api/index.html?com/lightbend/lagom/javadsl/client/ServiceClientGuiceSupport.html).

@[bind-hello-client](code/docs/services/client/Module.java)

Note that when you bind a server using `bindServices`, this will automatically bind a client for that service as well.
When using a client Lagom will need a [`ServiceInfo`](api/com/lightbend/lagom/javadsl/api/ServiceInfo.html) implementation and use it to identify itself to the remote service. When you are developing an application that's only implementing `ServiceClientGuiceSupport` to consume Lagom services you will need to invoke `bindServiceInfo()` and provide a `ServiceInfo` instance describing your app.

If you're already binding a service implementation using [ServiceGuiceSupport](api/index.html?com/lightbend/lagom/javadsl/server/ServiceGuiceSupport.html), this interface extends `ServiceClientGuiceSupport`, so your existing module can be used as is:

@[bind-client](code/docs/services/server/ServiceModule.java)

Note that when you bind a server using `bindServices`, this will automatically bind a client for that service as well. So in the previous example, when we start the application there will be one service (`HelloService`) and two clients (`HelloService` and `EchoService`) available.


## Using a service client

Expand Down
2 changes: 1 addition & 1 deletion docs/manual/java/guide/services/ServiceImplementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Having provided an implementation of the service, we can now register that with

@[hello-service-binding](code/docs/services/server/Module.java)

As you can see, the module extends Guice's `AbstractModule`, as well as Lagom's `ServiceGuiceSupport`. In this module, you can provide any Guice bindings you like. In this case, we're just providing a binding for the Guice service. Lagom allows you to bind multiple services, however, `bindServices()` may only be invoked once, as this will bind a router for Play to use to route Lagom service calls, which if bound multiple times, will cause a Guice configuration error.
As you can see, the module extends Guice's `AbstractModule`, as well as Lagom's `ServiceGuiceSupport`. In this module, you can provide any Guice bindings you like. In this case, we're just providing a binding for the `HelloService`. Lagom allows you to bind multiple services, however, `bindServices()` may only be invoked once, as this will bind a router for Play to use to route Lagom service calls, which if bound multiple times, will cause a Guice configuration error. The first binding you specify will be considered the `primaryServiceBinding` and its Descriptor's name will be used to name your Service. This name will be used by Lagom as the default value to identify your microservice when interacting with other microservices.

By convention, Play will automatically load a module called `Module` in the root package if it can find one, however if you'd prefer to call your module another name, or not put it in the root package, then you can manually add your module to Play's list of enabled modules by adding the following to `application.conf`:

Expand Down
25 changes: 25 additions & 0 deletions docs/manual/java/guide/services/ServiceInfo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Service Metadata

Service metadata, also referred to as `ServiceInfo`, includes a name and a collection of Service ACLs. Metadata is computed automatically in most scenarios and you won't need to review it or even provide it.

There are several scenarios supported by Lagom:

1. When you create a Lagom Service and you use the [[bindServices|ServiceImplementation]] method to register services Lagom will consider the first argument in `bindServices` to be the `primaryService`. The name of the primary Service will be used as the `ServiceInfo` name. Then Lagom will gather all the ACLs from all the Services you bind using `bindServices`. Finally, Lagom will bundle the `name` and the ACLs into a `ServiceInfo`.
2. When you create a Lagom Service but you don't bind any service. In this situation you can use the [[bindServiceInfo|ServiceClients#Binding-a-service-client]] method and provide the metadata manually.
3. When you consume Lagom Services from an app that already uses Guice, you simply [[bind a Service Client|ServiceClients#Binding-a-service-client]]. In this scenario Lagom is not bundling a ServiceInfo under the covers and you will have to provide one programatically.
4. The final scenario is that where the client app is not using Guice and connect to Lagom via the [[Lagom Client Factory|IntegratingNonLagom]]. In this scenario, Lagom will also create the metadata on your behalf.


## Service Name and Service ACLs

Services interact with each other. This interaction requires each service to identify itself when acting as a client to another service. When this identity is required, the `ServiceInfo`'s name is used by default. Take for example `HelloService`:

@[hello-service](code/docs/services/HelloService.java)

If Greetings Service packaged `HelloService` and Greetings Service was invoking I18n Service (not in the snippet) those calls would include the identity `hello` since that is the `HelloService` name (see `named("hello")`).

Services may publish ACLs in a Service Gateway to list what endpoints are provided by the service. These ACLs will allow you to develop [[Server-Side Service Discovery|ServiceDiscovery#Server-Side-Service-Discovery]] via a Service Gateway.

@[with-auto-acl](code/docs/services/UsersService.java)

In this example, the developer of `UsersService` set `withAutoAcl` to `true`. That is instructing Lagom to generate Service ACLs from each call's `pathPattern`. In this example, an ACL for `/api/users/login` will be created. When deploying, your tools should honour these specifications and make sure your API Gateway is properly setup.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package docs.services;

import com.lightbend.lagom.javadsl.api.*;
import akka.NotUsed;
import static java.util.concurrent.CompletableFuture.completedFuture;

public class EchoServiceImpl implements EchoService {


public ServiceCall<String, String> echo() {
return input-> completedFuture(input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package docs.services;

import com.lightbend.lagom.javadsl.api.Descriptor;
import com.lightbend.lagom.javadsl.api.Service;
import com.lightbend.lagom.javadsl.api.ServiceCall;
import com.lightbend.lagom.javadsl.api.transport.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.lightbend.lagom.javadsl.api.Service.*;

public interface UsersService extends Service {

ServiceCall<String, String> login();

@Override
//#with-auto-acl
default Descriptor descriptor() {
return named("users")
.withCalls(
restCall(Method.POST, "/api/users/login", this::login)
)
.withAutoAcl(true);
//#with-auto-acl
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package docs.services.server;

import docs.services.EchoService;
import docs.services.EchoServiceImpl;
import docs.services.HelloService;
import docs.services.HelloServiceImpl;

//#multi-service
import com.google.inject.AbstractModule;
import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport;

public class MultiServiceModule extends AbstractModule implements ServiceGuiceSupport {

protected void configure() {
bindServices(
serviceBinding(HelloService.class, HelloServiceImpl.class),
serviceBinding(EchoService.class, EchoServiceImpl.class));
}
}
//#multi-service
1 change: 1 addition & 0 deletions docs/manual/java/guide/services/index.toc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ServiceDescriptors:Service descriptors
ServiceImplementation:Implementing services
ServiceInfo:Service Metadata
ServiceClients:Consuming services
Test:Testing Services
MessageSerializers:Message serializers
Expand Down
8 changes: 8 additions & 0 deletions docs/manual/java/releases/Migration13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Lagom 1.3 Migration Guide

This guide explains hot to migrate from Lagom 1.2 to Lagom 1.3

## Service Info

It is now compulsory to provide a [[ServiceInfo|ServiceInfo]] programmatically to run in the Development Environment. If you were providing `lagom.play.service-name` and `lagom.play.acls` via `aplication.conf` on your Play application the Developer Mode's `runAll` will still work but this is now deprecated. If your Play app is not implementing a `Module` you should add one and use the new `bindServiceInfo` to setup the service name and `ServiceAcl`s. The suggested location is `<play_app_folder>/app/Module.java` so that Guice will find it automatically.

1 change: 1 addition & 0 deletions docs/manual/java/releases/index.toc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Migration12:Lagom 1.2 Migration Guide
Migration13:Lagom 1.3 Migration Guide
4 changes: 3 additions & 1 deletion docs/manual/scala/guide/services/ServiceImplementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Next we need to create an application cake. The simplest way of doing this is by

@[lagom-application](code/ServiceImplementation.scala)

The important method to implement here is the `lagomServer` method. Lagom will use this to discover your service bindings and create a Play router for handling your service calls. You can see that we've bound one service descriptor, the `HelloService`, to our `HelloServiceImpl` implementation. We've used Macwire's `wire` macro to wire the dependencies - at the moment our service actually has no dependencies so we could just construct it manually ourselves, but it's not likely that a real service implementation would have no dependencies.
The important method to implement here is the `lagomServer` method. Lagom will use this to discover your service bindings and create a Play router for handling your service calls. You can see that we've bound one service descriptor, the `HelloService`, to our `HelloServiceImpl` implementation. `LagomServer.forServices()` takes an variable number of arguments so you can build a server with many `Service`s in it (this is often useful to include admin or metrics services with your primary service). When you create a server with many services the first parameter in `LagomServer.forServices()` will be considered the `primary service` and Lagom will use it's name as the server name. So if you create a server with the services `Orders`, `Metrics` and `FraudDetection` then your server will be named `Orders` as that is the primary service.

We've used Macwire's `wire` macro to wire the dependencies - at the moment our service actually has no dependencies so we could just construct it manually ourselves, but it's not likely that a real service implementation would have no dependencies.

You can see that we've also mixed in the Play `AhcWSComponents` trait. Play's HTTP client, the WS API, which is used by Lagom for making service calls, is pluggable, and so an implementation needs to be selected when we wire our application together. We've selected Play's async-http-client implementation, provided by `AhcWSComponents`.

Expand Down
27 changes: 27 additions & 0 deletions docs/manual/scala/guide/services/ServiceInfo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Service Metadata

Service metadata, also referred to as `ServiceInfo`, includes a name and a collection of Service ACLs. Metadata is computed automatically in most scenarios and you won't need to review it or even provide it.

There are several scenarios supported by Lagom:

1. When you create a Lagom Service and you [[wire together the application|ServiceImplementation#Wiring-together-a-Lagom-application]] Lagom will consider the first argument in `LagomServer.forServices(...)` to be the `primaryService`. The name of the primary Service will be used as the `ServiceInfo` name. Then Lagom will gather all the ACLs from all the Services you wire. Finally, Lagom will bundle the `name` and the ACLs into a `ServiceInfo` and use that.
2. When you consume Lagom Services and mix-in `LagomServiceClientComponents` to [[bind clients|ServiceClients#Binding-a-service-client]] Lagom is not wiring a ServiceInfo under the covers and you will have to provide one programatically.
3. The final scenario is that where the client app is not using Guice and connect to Lagom via the [[Lagom Client Factory|IntegratingNonLagom]]. In this scenario, Lagom will also create the metadata on your behalf.



## Service Name and Service ACLs

Services interact with each other. This interaction requires each service to identify itself when acting as a client to another service. When this identity is required, the `ServiceInfo`'s name is used by default. Take for example `HelloService`:

@[service-name](code/ServiceInfo.scala)

If Greetings Service packaged `HelloService` and Greetings Service was invoking I18n Service (not in the snippet) those calls would include the identity `hello` since that is the `HelloService` name (see `named("hello")`).

Services may publish ACLs in a Service Gateway to list what endpoints are provided by the service. These ACLs will allow you to develop [[Server-Side Service Discovery|ServiceDiscovery#Server-Side-Service-Discovery]] via a Service Gateway.

@[service-acls](code/ServiceInfo.scala)

In this example, the developer of `UsersService` set `withAutoAcl` to `true`. That is instructing Lagom to generate Service ACLs from each call's `pathPattern`. In this example, an ACL for `/api/users/login` will be created. When deploying, your tools should honour these specifications and make sure your API Gateway is properly setup.


Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,4 @@ package servicecallcomposition {
//#filter-hello-service
}

}
}
Loading

0 comments on commit d9f4df0

Please sign in to comment.