-
Notifications
You must be signed in to change notification settings - Fork 33
Enveloper changes for Framework Version 5.x.x
With Framework 5 the Enveloper has been implemented with the Java Service Provider Interface (SPI). Framework 5 has two implementations that can be used, the default which is provided in the core classes and a test implementation that can be added for testing purposes.
The new Enveloper has a fluent interface and is easier to apply to unit tests.
The older injected Enveloper has been deprecated.
Previously the Enveloper would be injected into a Class. The example below is a simple example that uses the injected Enveloper to envelope the request payload and send to the "example.command.add-person" command in the Command Handler.
@ServiceComponent(COMMAND_API)
public class CommandApi {
@Inject
Sender sender;
@Inject
Enveloper enveloper;
@Handles("example.add-person")
public void addPerson(final JsonEnvelope envelope) {
sender.send(
enveloper
.withMetadataFrom(envelope, "example.command.add-person")
.apply(envelope.payload()));
}
}
Now the Enveloper is used by calling a static method on the Enveloper Interface, see below example:
@ServiceComponent(COMMAND_API)
public class CommandApi {
@Inject
Sender sender;
@Handles("example.add-person")
public void addPerson(final JsonEnvelope envelope) {
sender.send(
Enveloper.envelop(envelope.payloadAsJsonObject())
.withName("example.command.add-person")
.withMetadataFrom(envelope));
}
}
The above CommandApi can now be unit tested, first the following dependency needs to be added as test scope in your pom.xml:
<dependency>
<groupId>uk.gov.justice.services</groupId>
<artifactId>test-utils-enveloper-provider</artifactId>
<scope>test</scope>
</dependency>
The unit test below tests the Envelope sent has the correct metadata action name and payload.
@RunWith(MockitoJUnitRunner.class)
public class RecipeCommandApiTest {
@Mock
private Sender sender;
@InjectMocks
private RecipeCommandApi commandApi;
@Captor
private ArgumentCaptor<Envelope<JsonValue>> envelopeCaptor;
@Test
public void shouldHandleAddPersonRequest() {
final personId = UUID.randomUUID();
final String personName = "person name";
final JsonEnvelope jsonEnvelope = JsonEnvelope.envelopeFrom(
metadataWithDefaults().withName(name),
Json.createObjectBuilder()
.add("personId", personId)
.add("name", personName)
.build()
);
commandApi.addRecipe(jsonEnvelope);
verify(sender).send(envelopeCaptor.capture());
assertThat(envelopeCaptor.getValue(), jsonEnvelope(
withMetadataEnvelopedFrom(jsonEnvelope)
.withName("example.command.add-recipe"),
payloadIsJson(allOf(
withJsonPath("$.personId", equalTo(personId.toString())),
withJsonPath("$.name", equalTo(personName))
))).thatMatchesSchema()
));
}
}
Previously the Enveloper would be injected into a Class. The example below is a simple example that uses the injected Enveloper to envelope each event of a Stream from a Person aggregate, with the metadata from the original command.
@ServiceComponent(COMMAND_HANDLER)
public class CommandHandler {
@Inject
Enveloper enveloper;
@Inject
EventSource eventSource;
@Inject
AggregateService aggregateService;
@Handles("example.command.add-person")
public void addPerson(final Envelope<AddPerson> command) throws EventStreamException {
final UUID personId = getUUID(command.payloadAsJsonObject(), "personId").get();
final EventStream eventStream = eventSource.getStreamById(personId);
final Person person = aggregateService.get(eventStream, Person.class);
eventStream.append(
person.addPerson(personId)
.map(enveloper.withMetadataFrom(command)));
}
}
Now the Enveloper is used by calling a static method on the Enveloper Interface, see below example:
@ServiceComponent(COMMAND_HANDLER)
public class CommandHandler {
@Inject
EventSource eventSource;
@Inject
AggregateService aggregateService;
@Handles("example.command.add-person")
public void addPerson(final Envelope<AddPerson> command) throws EventStreamException {
final UUID personId = getUUID(command.payloadAsJsonObject(), "personId").get();
final EventStream eventStream = eventSource.getStreamById(personId);
final Person person = aggregateService.get(eventStream, Person.class);
eventStream.append(
person.addPerson(personId)
.map(Enveloper.toEnvelopeWithMetadataFrom(command)));
}
}
The Enveloper Interface is provided in the Framework API core artifact and the Enveloper SPI implementation is provided in the core Framework artifact:
<dependency>
<groupId>uk.gov.justice.framework-api</groupId>
<artifactId>framework-api-core</artifactId>
</dependency>
<dependency>
<groupId>uk.gov.justice.services</groupId>
<artifactId>core</artifactId>
</dependency>
The above CommandHandler can now be unit tested, first the following dependency needs to be added as test scope in your pom.xml:
<dependency>
<groupId>uk.gov.justice.services</groupId>
<artifactId>test-utils-enveloper-provider</artifactId>
<scope>test</scope>
</dependency>
The unit test below tests the Stream of Events that is appended to the EventStream:
@RunWith(MockitoJUnitRunner.class)
public class CommandHandlerTest {
@Mock
private EventSource eventSource;
@Mock
private AggregateService aggregateService;
@InjectMocks
private CommandHandler commandHandler;
@Before
public void setup() throws Exception {
createEnveloperWithEvents(PersonAdded.class);
}
@Test
public void shouldHandleAddPersonCommand() throws Exception {
final personId = UUID.randomUUID();
final String personName = "person name";
final String commandName = "example.command.add-person";
final messageId = UUID.randomUUID();
final EventStream eventStream = mock(EventStream.class);
final Envelope<AddPerson> command = Envelope.envelopeFrom(
metadataBuilder().withName(commandName).withId(messageId),
new AddPerson(personId, personName));
when(eventSource.getStreamById(personId)).thenReturn(eventStream);
when(aggregateService.get(eventStream, Person.class)).thenReturn(new Person(););
commandHandler.addPerson(command);
verify(eventStream).append(
argThat(streamContaining(
jsonEnvelope(
withMetadataEnvelopedFrom(command)
.withName("example.person-added"),
payloadIsJson(allOf(
withJsonPath("$.personId", equalTo(personId.toString())),
withJsonPath("$.name", equalTo(personName))
))).thatMatchesSchema()
)));
}
}