The Add service module workflow, ran in the previous step, created a HandleOccurrenceFilteringServiceDescriptor service descriptor in the repository’s services module.

The steps below flesh out the service’s descriptor with it’s input and output topics. Crucially, this will define the filter service’s input topic using the handle-occurrence-service’s output topic descriptor. This is one of the two key features of this tutorial. The other being system testing multiple services.

Define the topic resources

The aggregate template provided a shell service descriptor in the repository named HandleOccurrenceFilteringServiceDescriptor. Add the following to the class to declare the service’s input and output topics:

public final class HandleOccurrenceFilteringServiceDescriptor implements ServiceDescriptor {

    // 1. Hookup the handle-occurrence-service's output as this services, unowned, input.
    //    The JSON schema for the value remains owned by handle-occurrence-service, the service
    //    that owns the topic - it is only unowned from this service's point of view:
    public static final KafkaTopicInput<String, HandleUsage> HandleUsageStream =
            HandleOccurrenceServiceDescriptor.TweetHandleUsageStream.toInput();

    // 2. Define the filtered output topic, conceptually owned by this service. This creates a
    //    second, independently-owned JSON schema for the same HandleUsage type:
    public static final OwnedKafkaTopicOutput<String, HandleUsage> HandleUsagePresidentsStream =
            outputTopic(
                "twitter.handle.usage.presidents",
                HandleUsageStream.key().type(),
                HandleUsageStream.key().format(),
                HandleUsageStream.value().type(),
                HandleUsageStream.value().format(),
                withPartitions(3));

    ...
}

Importantly, note how this descriptor’s HandleUsageStream topic descriptor, (step #1 in the code above), is created by calling toInput() on the HandleOccurrenceServiceDescriptor’s TweetHandleUsageStream output topic descriptor. Compare this to the explicit declaration of the HandleUsagePresidentsStream output topic, (step #2 in the code above), which declares a new, previously unseen, topic, using outputTopic(...).

The toInput() method, called in step #1, returns an unowned input topic descriptor, with the correct name and types. Whereas, the output topic descriptor, created in step #2, is an owned topic descriptor, owned by this new filter service.

ProTip: The concept of topic ownership defines which service / aggregate, and hence team within an organisation, is responsible for the topic, its configuration, and the data it contains.

In this case, by declaring the filter services input by calling toInput() on the occurrence service’s output topic, we’re declaring the filter service as a downstream consumer of the occurrence service.

Conversely, though less common, service’s can define owned input topics. In such a situation, upstream services can declare that they produce to the topic by calling toOutput() on the input topic descriptor when declaring their own output topic descriptor.

The eagle-eyed of you may also have noticed that the service’s output topic, (step #2 in the code above), declares the topic’s key and value types and formats by referencing those of the input topic. This is just a convenient & type-safe way of ensuring the key and value types and formats of these two topics align, given that the output topic is simply a filtered view of the input topic.

ProTip: Ownership applies to a topic’s JSON schema too, and it follows the ownership of the topic. HandleUsageStream, (step #1 above), is an unowned input, so its JSON schema is tracked as unowned too: it remains owned by the handle-occurrence-service, which owns the topic it’s derived from. HandleUsagePresidentsStream, (step #2 above), is a new, owned, output, so even though it reuses the same HandleUsage Java type, it gets its own, independently owned, JSON schema, which can be evolved independently.

Updated: