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

Enveloper changes for Framework Version 5.x.x

Allan Mckenzie edited this page Mar 18, 2019 · 2 revisions

Introduction

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.

The New Enveloper in Action

Command API Old Injection Example

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()));
    }
}

Command API New Enveloper Usage

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));
    }
}

Command API Unit Testing

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()
        ));
    }
}

Command Handler Old Injection Example

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)));

    }
}

Command Handler New Enveloper Usage

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>

Command Handler Testing

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()
                )));
    }
}
Clone this wiki locally