-
Notifications
You must be signed in to change notification settings - Fork 33
Messaging between Service Components
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 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 interacting with the defined endpoints. As documented in the [TODO - RAML Classifier section] it is possible to create a RAML jar artefact for each service which contains its RAML definitions. This artefact can be used, included as a maven dependency to the raml-plugin to generate clients to communicate with the service.
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 for the Query API service.
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 in a synchronous manner, therefore the following information must be configured below:
- The generator required is the rest-client-generator (Http Synchronous Communications)
- The service component (i.e. the invoking component) into which the Requester shall be injected is the QUERY_API component.
- The target service that messages requests are sent to is the query-controller, therefore it is the RAML artefact from the query-controller that is included as a dependency.
<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 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);
}
}
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.
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);
}
}