Files
event-stream-processing-mic…/account/account-worker/README.md
Kenny Bastani 2af2788bc7 Cleaning up
2016-12-20 17:30:24 -08:00

7.0 KiB

Account Microservice: Worker

The account-worker application is a event stream processing application that listens for Account domain events as AMQP messages. The domain events that are generated by the account-web application are processed in this module.

The worker is responsible for durable transaction processing for work flows that are required to coordinate asynchronously with applications residing in other domain contexts.

The worker is also responsible for automatically remediating state changes in a distributed transactions that encountered a partial failure. The most important goal of the worker module is to keep the state of the system consistent through automated means — to guarantee eventual consistency.

Usage

The account-worker is a Spring Cloud Stream application that drives the state of the Account domain resource. The application is completely stateless because it uses hypermedia to drive the state of the application. At the heart of the account-worker is a configurable state machine that describes how domain events trigger state transitions on an Account resource.

The code snippet below describes a single state machine transition.

// Describe state machine transitions for accounts
transitions.withExternal()
        .source(AccountStatus.ACCOUNT_CREATED)
        .target(AccountStatus.ACCOUNT_PENDING)
        .event(AccountEventType.ACCOUNT_CREATED)
        .action(createAccount())

The /src/main/java/demo/config/StateMachineConfig.java class configures a state machine using the Spring Statemachine project. The snippet above describes the first transition of a state machine for the Account resource. Here we see that the source state is ACCOUNT_CREATED and the target state is ACCOUNT_PENDING. We also see that the state transition is triggered by an ACCOUNT_CREATED event. Finally, we see that an action method named createAccount is mapped to this state transition.

Each time an AccountEvent is received by the stream listener in the AccountEventStream class, a state machine is replicated by applying the ordered history of previous account events—we call this history the Event Log. Since each AccountEvent provides hypermedia links for retrieving the context of the attached Account resource, we can traverse to an account's event log and use a technique called Event Sourcing to aggregate the current state of the Account.

Functions

As we saw earlier in the configuration of state machine transitions, an action can be mapped to a function. In the StateMachineConfig class we'll find multiple bean definitions that correspond to transition actions. For example, earlier we saw the method triggered for a transition triggered by an ACCOUNT_CREATED event that mapped to an action named createAccount. Let's see the definition of that method.

@Bean
public Action<AccountStatus, AccountEventType> createAccount() {
    return context -> applyEvent(context,
      new CreateAccountFunction(context));
}

The createAccount method returns an executable action that passes the state context to a method named applyEvent. The applyEvent method is a step function that replicates the current state of an Account resource.

Since a state machine is replicated in-memory each time an AccountEvent is processed, we'll need to ensure that actions are not executed against the same resource multiple times during replication. The applyEvent method will only execute the supplied function—in this case CreateAccountFunction—if the state machine is finished replicating.

When the state machine is finished replicating, it will attempt to apply the AccountEvent for this context to an action mapped function. We can find each of the function classes for state transitions inside the /src/main/java/demo/function package.

.
├── /src/main/java/demo/function
    ├── AccountFunction.java
    ├── ActivateAccountFunction.java
    ├── ArchiveAccountFunction.java
    ├── ConfirmAccountFunction.java
    ├── CreateAccountFunction.java
    ├── SuspendAccountFunction.java
    ├── UnarchiveAccountFunction.java
    └── UnsuspendAccountFunction.java

The AccountFunction abstract class is extended by each of the other classes inside of the function package. Since we're using hypermedia to drive the state of the application, each function is immutable and stateless. In this reference application we can either define the task inside an AccountFunction class, or we can use a Consumer<T>, which is a Java 8 lambda expression, to apply an AccountEvent to an Account resource.

Let's go back to the StateMachineConfig class and look at an example of an action mapped function that uses a lambda expression.

@Bean
public Action<AccountStatus, AccountEventType> confirmAccount() {
    return context -> {
        // Map the account action to a Java 8 lambda function
        ConfirmAccountFunction accountFunction;

        accountFunction = new ConfirmAccountFunction(context, event -> {
            // Get the account resource for the event
            Traverson traverson = new Traverson(
                    URI.create(event.getLink("account").getHref()),
                    MediaTypes.HAL_JSON
            );

            // Follow the command resource to activate the account
            Account account = traverson.follow("commands")
                    .follow("activate")
                    .toEntity(Account.class)
                    .getBody();
        });

        applyEvent(context, accountFunction);
    };
}

The snippet above shows the definition of the confirmAccount action. Here we see a stateless function that uses a Traverson client to follow hypermedia links of the AccountEvent resource in a workflow that activates the Account. Since the embedded hypermedia links provide the full context of an AccountEvent resource, we can implement this function from anywhere—even as a serverless function!

Serverless Functions

A serverless function is a unit of cloud deployment in a PaaS (Platform-as-a-Service) that is composed of a stateless function. Serverless was first popularized by Amazon Web Services as a part of their AWS Lambda compute platform.

Serverless—which is also referred to as FaaS (Function-as-a-Service)—allows you to deploy code as functions without needing to setup or manage application servers or containers.

With a serverless function, a cloud platform will take care of when and where a function is scheduled and executed. A cloud platform is also opinionated about the compute resources required to execute and/or scale a function.

In this reference architecture we have two units of deployment per microservice, a web and worker application. Each of the workloads for the deployments are designed to operate in an immutable Linux container. Since the state machine actions that are mapped to the AccountFunction classes in the account-worker application are both immutable and stateless, we can choose to instead map each of these actions to a serverless function.