1st tutorial - Spring
Table of contents
Introduction
This tutorial is an introduction to writing event driven application logic using Fluxtion and Spring. The reader should be proficient in Java, maven, git and possess a basic knowledge of Spring dependency injection. Spring is a very popular dependency injection container, this example demonstrates Fluxtion Spring integration.
Our goal is to build the logic for a simple lottery application that will be connected to request and response queues.
- Logic resides in user classes and functions
- Spring config declares which beans to wire together
- Fluxtion creates an event processor that manages the beans and dispatches events to the correct bean instance
This example is focused on building event driven processing logic and not the connection to real queues.
At the end of this tutorial you should understand how Fluxtion:
- Exposes service interfaces for managed components
- Calls lifecycle methods on managed components
- Triggers event logic between dependent components
- Wires components together using Spring configuration
Example project
The example project is referenced in this tutorial.
The Lottery game
A lottery game sells tickets to customers from a ticket shop, the shop is either opened or closed. A customer receives a receipt for a purchased ticket or a message that no ticket was purchased. Tickets must have six numbers and cannot be bought when the shop is closed. A lottery machine picks the winning ticket number from the tickets purchased and publishes the lucky number to a queue.
Designing the components
Our application will be event driven through a service interface api for the outside world to code against. We must first think about the design of our services and then the concrete implementations. Once this design is complete we will use Fluxtion to wire up the components. Fluxtion is low touch allowing engineers and architects to concentrate on design and components with no distraction.
Processing logic
Our design sketches show what we intend to integrate into our system
flowchart TB
classDef eventHandler color:#022e1f,fill:#aaa3ff,stroke:#000;
classDef graphNode color:#022e1f,fill:#00cfff,stroke:#000;
classDef exportedService color:#022e1f,fill:#aaa3ff,stroke:#000;
style EventProcessor fill:#e9ebe4,stroke:#333,stroke-width:1px
buyTicket><b>ServiceCalls</b>\n buyTicket, openStore, closeStore, setTicketSalesPublisher]:::eventHandler
selectWinningTicket><b>ServiceCalls</b>\n selectWinningTicket, setResultPublisher]:::eventHandler
LotteryMachine([<b>ServiceLookup</b>::LotteryMachine]):::exportedService
TicketStore([<b>ServiceLookup</b>::TicketStore]):::exportedService
TicketStoreNode[TicketStoreNode\n <b>ExportService</b>::TicketStore]:::graphNode
LotteryMachineNode[LotteryMachineNode\n <b>ExportService</b>::LotteryMachine]:::graphNode
selectWinningTicket ---> LotteryMachine
buyTicket --> TicketStore
LotteryMachine --> LotteryMachineNode
TicketStore ---> TicketStoreNode
subgraph EventProcessor
TicketStoreNode --> LotteryMachineNode
end
Spring config
Spring config for our lottery application
Service api
From our business problem we have identified a concrete data type Ticket and two public services TicketStore and LotteryMachine. Now we have identified the top level concepts we can create a service api that client code will use to drive the system.
Our interfaces separate concerns logically making the api simple to work with. The methods setTicketSalesPublisher and setResultPublisher connect the results of processing to output queues or a unit test. One of our goals is to make the logic easy to test with the minimum of infrastructure.
Implementing logic
We implement our two interfaces with concrete classes TicketStoreNode and LotteryGameNode using some lombok annotations to remove boilerplate code.
TicketStoreNode
The TicketStoreNode implements TicketSore and supports logic to buy and sell tickets depending on the state of the store . A lifecycle method start is created that checks the ticketSalesPublisher has been set before progressing any further. TicketStoreNode also implements Supplier<Ticket> which allows any child component to access the last sold ticket without accessing the concrete type. Making components reference each other through interfaces is good practice.
LotteryGameNode
The LotteryMachineNode implements LotteryMachine and supports logic to run the lottery. LotteryMachineNode holds a reference to an instance of Supplier<Ticket> and whenever processNewTicketSale is called, acquires a purchased ticket and adds it to the internal cache. A lifecycle method start is created that checks the resultPublisher has been set before progressing any further.
The lifecycle methods and how clients access the TicketStore and LotteryMachine services are described below.
Building the application
Now we have our service interfaces designed and implemented we need to connect components together and make sure they provide the functionality required in the expected manner. There are several problems to solve to deliver correct event driven functionality:
- How do clients access the components via service interfaces
- How are the lifecycle methods called
- How is LotteryGameNode#processNewTicketSale called only when a ticket is successfully purchased
- How are the components wired together
Fluxtion solves these four problems for any event driven application.
Exporting services
We want clients to access components via service interface, this is simple to achieve by adding an @ExportService annotation to the interface definitions on the concrete classes, as shown below.
Fluxtion will only export annotated interfaces at the container level, in this case Fluxtion will not export the Supplier<Ticket> interface that TicketStoreNode implements.
Accessing exported services
Once the service interface has been marked for export client code can locate it through the EventProcessor instance that holds the application components by calling EventProcessor#getExportedService. Client code invokes methods on the interface and Fluxtion container will take care of all method routing.
Event dispatch
When a ticket has been successfully purchased the LotteryMachineNode instance method processNewTicketSale is invoked by Fluxtion. The processNewTicketSale method grabs the last ticket sale from the Supplier<Ticket> reference and adds it to the cache. Fluxtion knows to trigger a method if it is annotated with @OnTrigger and one of its dependencies has been triggered from an incoming client service call.
How does Fluxtion know to invoke this method at the correct time? The container maps the dependency relationship between TicketStoreNode and LotteryMachineNode, so when an exported service method is invoked on TicketStoreNode Fluxtion calls the processNewTicketSale trigger method on LotteryMachineNode. This is great as it removes the need for the programmer to manually call the event dispatch call graph.
The next problem is we only want the processNewTicketSale method called when a ticket is successfully purchased. If we try to add a ticket when the openStore is called a null pointer exception will be thrown at runtime. How can the developer control the propagation of dependent trigger methods?
Fluxtion manages exported service event propagation in two ways:
- boolean return type from the service method, false indicates no event propagation, true propagates the notification
- annotate the method with @NoPropagateFunction annotation
Both propagation controls are used in LotteryMachineNode ensuring the LotteryMachine is only triggered on successful ticket purchases. The TicketStoreNode#buyTicket is the only method that will trigger an event notification to LotteryMachineNode and only if the ticket passes basic validation and the store is open.
Lifecycle methods
Applications often benefit from lifecycle methods such as init, start and stop, allowing checks to be carried out before executing the application. Fluxtion supports init, start and stop by annotating a method with an annotation @Start @Stop or @Initialise. We use the start method in our application to check output receivers ticketSalesPublisher and resultPublisher have been set by the client code.
Client code invokes the lifecycle method on the container Fluxtion then calls all the lifecycle methods registered by components in the right order.
Wiring the components together
The dependency injection container wires components depending upon the configuration supplied. As Fluxtion natively supports
spring ApplicationContext we use a spring configuration file in this example to wire the TicketStore to the LotteryMachine.
We are using Spring in these tutorials because of its familiarity to readers, spring is not required by Fluxtion when using
other methods to specify container managed beans.
Fluxtion provides a spring extension for building a container using static helper methods. The built container is free of any spring dependencies, Fluxtion just reads the spring file to drive its own configuration. To build the container the tutorial loads the spring file from the classpath:
Build system
The example use maven to build the application, the Fluxtion runtime dependency is pulled in transitively via the compiler. Lombok is added to reduce boilerplate code, spring-context enables reading the spring config file, both of these dependencies are optional in vanilla Fluxtion usage.
Running the application
Running the application requires the following from client code:
- Building the container using the spring config file
- Call lifecycle methods on the container
- Lookup container exported service interfaces and store the references for use in client code
The fact the components are managed by a container is completely hidden from the client code, this makes integrating Fluxtion into an existing system extremely simple as no new programming models need to be adopted.
In our example the main method only interacts with the business logic via the service interfaces, in a real application the methods would be invoked by taking commands from an incoming request queue.
Executing our application produces the following output:
Conclusion
We have quite a lot of ground in a seemingly simple event driven application. Hopefully you can see the benefits of using Fluxtion to write event driven business logic:
- Forcing client code to interact with components through interfaces
- Formal lifecycle phases that are easy to plug in to
- Dispatch of events to any bean managed instance exporting a service
- Automatic dispatch of dependent trigger methods
- Removal of state and conditional dispatch logic from business code
- Deterministic event handling that is well tested
- Control of event propagation with simple boolean returns or annotations
- More time spent writing logic and less time writing infrastructure
- Simple programming model that leverages Spring for quick adoption
As applications grow and become more complex programming event driven logic becomes ever more expensive. The benefits of a tool like Fluxtion really shine during the growth and maintenance phase.
I hope you have enjoyed reading this tutorial, and it has given you an understanding of Fluxtion and a desire to use it in your applications. Please send me in any comments or suggestions to improve this tutorial