Skip to content
This repository was archived by the owner on Aug 13, 2020. It is now read-only.

Messaging between Service Components

matt-rich edited this page Dec 19, 2016 · 9 revisions

Introduction

The framework has been designed with microservices in mind, separating the individual Service Components (Command API, Query View etc.) into encapsulated cohesive microservices that communicate over via Http or JMS.

Each service defines its API through its RAML definition(s) and the framework is capable of harnessing these RAML definitions to generate clients capable of communicating with each other. As documented in the <RAML documentation section - TODO> each service can generate a jar file containing its RAML definitions, which can then be used as a maven dependency to allow client services to communicate with it.

Generating Clients

The framework can generate clients to communicate with a service, based on its RAML definitions. This avoids the need for manually creating REST or JMS clients for allowing cross service communications.

The generated clients are written to the target/generated-sources directory and should not be altered manually as any changes will be overwritten. These clients are available as CDI beans within the application context ready to be injected and used as required within your service.

To use one of the generated clients you need to use the @Inject annotation over either a Requester or Sender interface, depending on whether synchronous or asynchronous communications are required.

@ServiceComponent(QUERY_API)
public class RecipesQueryApi {

    @Inject
    Requester requester;

    @Handles("example.get-recipe")
    public JsonEnvelope getRecipe(final JsonEnvelope query) {
        return requester.request(query);
    }
}

Usage/Implementation

The framework uses the JsonEnvelope (passed as the parameter) to determine which endpoint a message should be sent to. The name of the action defines the endpoint and the payload will become the payload of the message with any URL parameters extracted as required. In the example above the api and controller have identical RAML definitions for the "example.get-recipe" endpoint therefore the original envelope can be passed through without modifications. Typically the Enveloper should be used to generate a new envelope with the required information.

Binding Annotations

The framework will bind and inject the appropriate client to the implementing class through the use of framework annotations coupled with the POM configuration (Details below). Either the @ServiceComponent or @FrameworkComponent annotation should be present on the class or directly on the injected Requester/Sender member variable. Where the client injection is occurring within one of the core ServiceComponent services (e.g. in a handler class) then the @ServiceComponent annotation should be used, whereas the @FrameworkComponent is used for injecting in all other classes.

@Remote
@FrameworkComponent("QUERY_API")
public final class RemoteCakeshopQueryController {
  private static final String BASE_URI = "http://localhost:8080/example-query-controller/query/controller/rest/cakeshop";

  @Inject
  RestClientProcessor restClientProcessor;

  @Inject
  RestClientHelper restClientHelper;

  @Inject
  Enveloper enveloper;

  @Handles("example.get-recipe")
  public JsonEnvelope getExampleRecipe(final JsonEnvelope envelope) {
    LoggerUtils.trace(LOGGER, () -> String.format("Handling remote request: %s", envelope));
    final String path = "/recipes/{recipeId}";
    final Set<String> pathParams = restClientHelper.extractPathParametersFromPath(path);
    final Set<QueryParam> queryParams = new HashSet<QueryParam>();
    final EndpointDefinition def = new EndpointDefinition(BASE_URI, path, pathParams, queryParams, "example.recipe");
    return restClientProcessor.get(def, envelope);
  }
}

POM configuration

To generate the client beans from the target service RAML definition we must use the raml-maven-plugin, and either the rest-client-generator or messaging-client-generator depending on whether the Requester or Sender is required respectively. Much of the configuration is common, with the following example focusing on the key variables, which are the RAML dependencies for the target service and the serviceComponent property that is used in the annotation bindings. For the complete configuration see the example-application pom.xml

<plugin>
    <groupId>uk.gov.justice.maven</groupId>
    <artifactId>raml-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>rest-client-generator</id>
            <configuration>
                <generatorProperties>
                    <serviceComponent>QUERY_API</serviceComponent>
                </generatorProperties>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>uk.gov.justice.services.example</groupId>
            <artifactId>example-query-controller</artifactId>
            <version>${project.version}</version>
            <classifier>raml</classifier>
        </dependency>
    </dependencies>
</plugin>

The tiers/layering of the framework as demonstrated by the module structure in the example application show that the Query API service will communicate with the Query Controller, so it is the query-controller RAML dependency that is defined. Being the query column this messaging format is synchronous so we need a rest generator which will back the injected Requester.

Clone this wiki locally