fork and split gateway for a parallel execution of activities
compensation activity
compensation logics
automatic trigger of compensation on exception
reverse order on compensation
store, read, and pass variables
save point after each activity for fault tolerance (e.g., DB or file system)
visualization of transactions for debugging and manual compensation purposes
Scenario:
create pending order
reduce inventory
fulfil payment
publish order
Start a saga instance:
// start saga instance from saga model "order creation"
orderCreationSaga = sagaFactory.startNewInstanceFromSagaModel("order creation")
orderId = orderCreationSaga.getVariables().getOrWait("orderId", 500, Millis)
orderCreationSaga.waitForCompletion(3_000, Millis)
The task to execute a single saga step:
public SagaStepRunnable(SagaInstance sagaInstance, SagaActivity sagaActivity) {
this.sagaInstance = sagaInstance;
this.sagaActivity = sagaActivity;
}
@Override
public void run() {
SagaLog sagaLog = sagaInstance.getSagaLog();
try {
sagaActivity.execute(sagaInstance.getVariables());
// if the thread crashes at this point, the saga restarts after the last successful activity.
// hence, all activities (local and remote) must be idempotent.
sagaLog.storeSuccessEventFor(sagaActivity, sagaInstance.getVariables());
} catch (Exception e) {
sagaLog.storeFailureEventFor(sagaActivity, sagaInstance.getVariables(), e);
sagaInstance.switchToCompensationMode();
}
sagaInstance.scheduleNextActivities();
}
Questions:
necessary to sync “entity creation for order” with “current variables” and “success event”?
The transactional outbox pattern allows to update the state of a database and to send a message to a message broker in one single transaction. In this way, either both or none of these actions happen. In other words: they are executed atomically. Details of this pattern can be found here: https://microservices.io/patterns/data/transactional-outbox.html.
There are numerous approaches to implement this pattern. In the following, we discuss those approaches that are most reasonable to me.
Polling Publisher
Using a Scheduled Database Poller
Polling the database in a regular time intervall is the straight-forward implementation of the “Polling Publisher”.
@Transactional
@Retryable(..)
public void send(OutboxEvent event) {
this.kafkaTemplate.send(..);
this.repository.deleteById(event.getId());
}
Attention must be paid to a multi-instance operation. If multiple instances share the same database, they also share the same outbox table. As a consequence, they interfere with each other when processing outbox entries.
Advantages: This approach is easy to implement and requires no special infrastructure.
Disadvantages: The scheduler performs polling and thus stresses the database unnecessarily. Moreover, running multiple instances requires a more complex implementation to keep the order of events.
Requirements: Apparently, this approach requires a database table to store the outbox events. Moreover, it requires a poller – either executed by a thread or by a dedicated application.
What about using an in-memory queue as optimization to avoid polling?
Unfortunately, this approach either looses events or falls back to polling depending on whether the notification is passed after or within the transaction.
Using a Workflow Engine
Bernd Ruecker proposes to use a workflow engine in order to implement the transactional outbox pattern. The corresponding process model is illustrated in the following figure.
The underlying workflow engine remembers the position in the process model, so that it does not execute tasks again which were already executed completely. In this way, the workflow engine resumes with the task that has not been finished and should be processed next according to the process model.
Hence, If the application crashes before completing the database operation task, the process instance resumes at this point and re-executes the database operation task. If the application crashes before completing the notification task, the process instance resumes at this point and re-executes the notification task.
Strictly speaking, this approach does not represent the pattern anymore, because it does not make use of an outbox table. Nonetheless, it puts the pattern’s underlying idea into practice.
Advantages: This approach requires neither a dedicated outbox table, nor special infrastructure or a custom scheduler. Moreover, we can make use of the monitoring and operations capabilities of the workflow engine tooling to debug pending tasks.
Disadvantages: The integrated job scheduler of the workflow engine still performs polling. Moreover, this approach does not keep the order of events.
Requirements: Apparently, this approach requires a workflow engine. If the engine and the process model should be hidden from the application developer, a transactional outbox API is recommended that encapsulates these implementation details.
Why does this approach work?
The alternative approaches remember the intent to send an event by storing an entry in the outbox table. In contrast, this approach remembers the intent by storing the current position and the event’s data in the process instance. Of course, this data is also represented by database tables. However, these tables are managed automatically by the workflow engine, and not by the application developer.
Spring offers the annotation @Scheduled to define a task and its corresponding scheduling, e.g., execute this method every 5 minutes. The annotation saves you a great deal of work: in the background, it creates or looks up a scheduler, creates a task which invokes your method, and passes the task to the scheduler with your scheduling arguments (here: every 5 minutes).
Scheduling Parameters
The annotation @Scheduled allows to specify a fixed delay (in ms), a fixed rate (in ms), or a more flexible cron expression if the first two options are not expressive enough for your use case. The following code snippet shows an implementation of our “every 5 seconds”-example from above:
@Component // or any subtype like @Service
public class AnyComponent {
private static final Logger log = LoggerFactory.getLogger(AnyComponent.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() { // visibility is irrelevant: even private is possible
log.info("The time is now {}", dateFormat.format(new Date()));
}
}
Task Method Requirements
The method, which is annotated with @Scheduled, must fulfill the following requirements:
The enclosing class of the method must be a Spring component. That is, the class must be annotated with the annotation @Component or an annotation which includes @Component like @Service, for example.
The method must be void.
The method must have no parameters.
The method may be private.
Read Parameter Values from a Properties File
If you do not want to specify the delay or the rate directly in the code, you can read it from the configuration context. For this purpose, Spring provides the parameters fixedDelayString and fixedRateString. The following example reads the seconds of the delay from the configuration key my.property.fixed.delay.seconds. If the value is not available or invalid, a default value of 60 is used. Since the delay expects a value in milliseconds, 000 is appended to the read value.
// with a default value of 60
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds:60}000")
Further examples:
// without a default value
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds}000")
// without appending
@Scheduled(fixedDelayString = "${my.property.fixed.delay.milliseconds}")
// hard-coded value as string (not recommended due to missing static type checking)
@Scheduled(fixedDelayString = "7000")
With these *String variants, you can define the delay or, respectively, the rate from aproperties file. Note that the value is read only once at startup time. Thus, this approach is still static. You cannot change the value at runtime. We refer to the Javadoc of @Scheduled for more detailed information on the parameters.
Dynamic Scheduling
If you need to read your scheduling arguments dynamically, that is, at runtime, then you @Scheduled is not sufficient. Instead, your can use Spring’s interface TaskScheduler. Declare a field with this type and annotate it with Spring’s @Autowired annotation. Then, you can pass a dynamically created task to the scheduler and specify whether it should be executed once or repeatable with a fixed delay or at a fixed rate. We refer to the documentation of the TaskScheduler for more information.
In the following list, we name each join point with its corresponding pointcut because it was initially not apparent to me how to capsure all calls (or executions). You cannot use one single * wildcard to match all method and constructor calls. You need to define a composite pointcut which capsures on the one hand all method calls and on the other hand all constructor calls. This differentiation by AspectJ is not directly reflected by the documentation, but only by AspectJ’s different pattern syntax for call (or execution). To capsure method calls, you need to use MethodPattern. To capsure constructor calls, you need to use ConstructorPattern. The complete syntax for all patterns of AspectJ can be found here.
AspectJ is an open-source framework that allows you to write Java code following the aspect-oriented programming (AOP) paradigm. AOP provides an alternative way of programming in the following situation:
Let us assume you want to implement some functionality which is required at multiple locations in your application’s source code. If we follow the object-oriented programming (OOP) paradigm, we would proceed as follows. First, we declare a new class. Then, within this class, we declare a new method which implements the required functionality. If necessary, we add some associated fields. Afterwards, we place an invocation of the method (together with the class instantiation) at each of the required locations in our source code.
If we follow the AOP paradigm, we would proceed as follows. First, we declare a new aspect which is similar to a class. Then, within this aspect, we declare a new advice which corresponds to the method from above. If necessary, we add some associated fields to the aspect. So far, we have proceeded exactly as if we had followed the OOP paradigm – only with different terms. But now, we do not place an invocation of the advice at each of the required locations. Instead, we write down these locations in our aspect and pass it to an additional compiler – the AOP compiler. This AOP compiler reads in the aspect and automatically places an invocation of the advice at each of the designated locations. Thereby, it also handles the instantiation of the aspect. The set of our locations is called a pointcut and is associated with our advice. In this way, the AOP compiler knows which code it should place at which locations. However, most AOP compilers cannot insert advices at arbitrary locations, but only at well-defined locations. For example, the AspectJ compiler can insert advices at method calls and field accesses, but not at loops or if-statements. Such well-defined locations are called joinpoints. Thus, a pointcut is not a set of arbitrary locations, but a set of joinpoints.
Hence, an aspect is not instantiated and accessed directly from within your source code. Instead, it is automatically inserted into your source code by the AOP compiler. For this reason, we do not term it class, but aspect. In summary, the AOP paradigm allows to insert some functionality at multiple locations in your application without the effort of touching the source code at each individual location by hand. In this way, it provides a clean modularization of crosscutting concerns and thus serves as a complement to the OOP paradigm.
AOP terms in short:
A jointpoint is a location in code at which the AOP compiler is able to automatically insert new code either before, after, or around (i.e., before and after). Valid joinpoints in AspectJ are, for example, method declarations, method calls, and field read/write accesses. So far, AspectJ does not support inserting code at loops and if-statements. So, AspectJ does not consider these constructs as joinpoints.
A pointcut is a set of joinpoints at which we want to insert the same new code. This set of joinpoints is not described as an enumeration of each and every individual joinpoint, but instead as a single compact expression similar to a regular expression.
An advice contains the new code which we want to insert at multiple joinpoints in our application’s source code.
An aspect contains one or more advices and their associated pointcuts. By default, AspectJ creates and uses one instance per aspect. So, you can consider an aspect as singleton. However, you can change this behavior to a per-object or per-joinpoint base (see aspect instantiation for more details).
Since I work at the Kiel University, I teach UML to the students in the 3rd and 4th semester of computer science. At the latest when they need to work out a UML component diagram and a corresponding deployment diagram, the following questions arise:
What is a UML software component? What is the difference between a class, a package, and a component?
For this reason, we address these questions in this post. First, we look at some definitions to distinguish a component from other UML entites.
Component vs. (Class, Package, and Library)
Let’s start with the most important message of this post: Consider a component as a composition of classes, packages, and sub components that is accessible only by other components and only via interfaces.
TODO
Communication within and between components
TODO UML notation
Example Component Diagram
When creating a component diagram, remember that it must depict additional semantics compared to all other diagram types such as class and package diagrams. Otherwise a component diagram is useless and can be thrown away.
TODO diagram with components (android, server, db) and sub components: A3, User, Role, ORM, …
Example Component Representations in Java
Since the Java programming language does not provide the abstraction of components, we need to represent it with the means Java offers us.
Hence, we discuss the three following representations of a component possible in the Java universe.
The class-based approach
The package-based approach
The library-based approach
Due to Java’s direct support for interfaces, we represent a component’s interface by one or more Java interfaces.
The class-based approach
In this approach, we represent a component by a class that implements all interfaces the corresponding component should provide.