EBX and Design Patterns

Christian Martin
9 min readJul 21, 2023

--

The Gang of Four has written great documentation on how to improve our software development. Design Patterns increase maintainability, testability, productivity and fun in working. They suite Object Oriented Programming so well, yet, often projects contain a script-ish way of coding. Especially projects where functionality is added via extension classes.

This document elaborates on design patterns which have been suitable while developing new functionalities for clients using the Master Data Management solution of Tibco EBX. Of course, this can vary among projects.

To goal of this story is to help you:

  1. Adhere to the principle of separation of concerns.
  2. Promote re-usable code.
  3. Promote robust maintainable code.
  4. Promote recognizable code.

Treat ‘Extension’ classes as controllers

Often new functionalities in EBX start from an extendable class, or an interface. Often the code is completely or almost completely written in that particular class, violating various important principles of writing good code, as, for example, outlined in various important books.

Code is not decoupled, not being re-used and also hard to re-use. Decoupled code is one of the pillars of good code. In addition, using design patterns and the ‘controller’ as starting point of your functionality creates smaller pieces of code, hence easier to read and testable (Another story on that soon)!

What does that mean “Treat ‘Extension’ classes as controllers”? A controller is basically nothing more than a class that receives a request and delegates to the correct classes supposed to perform certain functionality.

So what are you supposed to do here? Get your dependencies and provide them to the executing class. Nothing more! Are you supposed to work on a single record provided by the extension class, or a bunch? Get the them now, and delegate them to the right class for execution. In most cases a use case does the trick of your functionality. Do you need to persist anything for this functionality, initialize your repository class now!

A very simple example:

public class UpdatePersonExampleScript extends ScriptTaskBean {

// your data context
private String recordId;

@Override
public void executeScript() {

Adaptation dataSet = getDataSet();
// more on the the repo pattern below
PersonRepository personRepository = new PersonRepository(dataSet);

// more on the use case practise later in the article
UpdatePersonUseCase updateUserUseCase = new UpdatePersonUseCase(personRepository);

updateUserUseCase.execute(recordId);

}

private Adaptation getDataSet() {
// implement
}

// getters and setters for injected data context

}

Here you go. A very simple class to extend EBX functionality. There is hardly any logic, it only gets the dependencies required and delegates it to a class (the use case) to do the actual trick.

Useful Design Patterns

The list below are patterns often valuable, giving answers to many cases. The more you apply them, the sooner you recognize when to use which. A very useful capability to become more productive and quickly implement complex cases.

  1. Use cases
  2. Repository
  3. Command
  4. Chain of responsibilities
  5. Factory
  6. Strategy

Let’s go over them, keeping the tastiest piece of the pie for the last.

Use cases

In many Spring (Boot) applications you’ll find these as services. However, services are already taken by EBX and following Domain Driven Design I prefer to call them Use cases as well in EBX.

They simply reflect the functionality being executed. To keep them aligned make a UseCase interface. For example:

public interface UseCase<T, U> {

T execute(final U aNiceName);

}

In most cases (almost all) the controller calls an implemented class of this interface to be executed.

Finally, the term use case suggests you are going to cover a certain business functionality here. Easy to read and find certain cases perhaps in your project.

Repository

Keep responsibilities where they are supposed to be! Does it have to deal with persistence? You want to get a certain association, or do you want to persist data? Keep that functionality in a repository. This ensures the responsibility of persistence is allocated to the given class, instead of having that all over your application. It avoids a lot of mess. In addition, and this applies to all patterns, everyone in the project knows: If I have to deal with persistence I simply go to the corresponding repository. No need to take a look anywhere else. That last bit sounds nice, right? No duplication. No need to go over much of code to find the spot where the persistence is taking place.

There are various options to create the repository. Let’s consider a very generic one:

public class EbxRepository {

private final Adaptation dataSet;

// for persistence you'll need a session and dataspace, but just as an example only a dataset here.
public EbxRepository(Adaptation dataSet) {
this.dataSet = dataSet;
}

public findById(final Integer id) {
// your implementation here
}

public save(ValueContext valueContext) {
// your implementation here
}

}

If you like a more Domain Driven Design-ish approach you might want to create repositories per association. You could extend the EbxRepository with the generic functions to your association repository. For instance:

public class SupplierRepository extends EbxRepository {

// implementation of specific methods and so on
}

I’ll leave the fantasy on how to up to you.

Command

Commands are generally small tasks to be executed. More information available here. It could be a simple task to copy data from a certain association. Or increasing a certain value or something to validate. Let’s stick with the last example. It can be anything specific.

Again: Create an interface (useful for the chain of responsibilities!)

public interface Command<T> {

void execute(T value);
}

Implement it for a certain validation:

public class ValidateExistenceCommand implements Command<Adaptation> {

// constructor

@Override
public void execute(Adaptation record) {

if (isNonExisting(record)) {
throw OperationException.error("Whoops this is not a record! We can't find it");
}
}

// implement required method
}

You can call this command in your UseCase and see: You already have very tiny pieces of code very specific for the given actions.

Chain Of Responsibilities

Now commands become more interesting. Do you need multiple data transfer actions, for each association your record exists of? Or various validation checks? Chain the commands that perform that tiny simple task using the design pattern Chain Of Responsibilities. As usual the Refactoring Guru has documented it very well here.

There are quite some ways to chain, but eventually you want to have a class that is able to execute commands sequentially. Let’s assume we have another validator that checks if the supplier has an address. We call that ValidateAddressCommand . The chain can look as follows:

public CommandHandler {   

private final List<Command> commands = new ArrayList<>();

public CommandHandler addCommand(Command command) {
if (command != null) commands.add(command);
return this;
}

public void execute() throws Exception {
for (Command command : commands) {
if (command != null) command.execute();
}
}
}
public class SupplierValidatorChain {

// constructor

public void createAndExecuteChain(Adaptation record) {

ValidateExistenceCommand validateExistenceCommand = new ValidateExistenceCommand();
ValidateAddressCommand validateAddressCommand = new ValidateAddressCommand();

commandHandler
.addCommand(validate)
.addCommand(validateAddressCommand)
.execute(record);
}
}

Now in your use case that has to validate a supplier and persist it you could do it as short as follows:

public class CreateSupplierUseCase implements UseCase<Void, Adaptation> {

public CreateSupplierUseCase(SupplierRepository repo, SupplierValidatorChain supplierValidatorChain) {
// implement
}

public Void execute(Adaptation record) {

supplierValidatorChain.createAndExecute(record);

repo.save(record);

return null;

}
}

Hopefully this example already illustrates how the script-ish way of writing has changed to separated code with very specific tasks, small and reusable.

Factory

With the factory design pattern one can return a certain implementation given a condition that is predefined.

Consider the following example for illustration purposes: We have a person, who can be treated as an employee or customer. The interface could be a person, that is implemented by the Employee class and Customer class. They both get their additions.

With the factory you could retrieve them given the context, in this case a workflow. The factory can be very suitable for strategies which the next section elaborates on.

public class PersonFactory {

public Person get(final String workflow) {

if (workflowIsForEmployees()) {
return new Employee();
}

return new Customer();
}
}

This was just a simple example, or re-cap, on how to write a small Factory. Let’s go the more interesting part: Strategies.

Strategies

Yeah finally! This design pattern makes working with EBX so much better. Keep the best for the last! We are going to combine most of the above here.

Here is the case: You’ll have a staging table to which an external source sends data related to products. However, it has multiple processes applying subsets of the complete dataset. You could create multiple stagings, multiple workflows or you can use this great design pattern. Create a staging for a product. In addition let the external source provide the process that provides the message know to the staging and we are ready to create a very generic workflow where all of the above comes together.

Each process has its own mapping, lets’ pick two processes: Create Product and Adjust Description.

The product (Staging) contains the following information:

  1. Article Number
  2. Name
  3. Description

For the process Create Product all the fields are send to EBX, for the process Adjust Description only Article Number (As identifier for example) and Description. This means we need to strategies in total, one for each process that has to map different attributes.

Let’s build the Strategy pattern. This particular case only executes, but you could, of course, add multiple functionalities.

Let’s create the interface for our strategies first.

public interface ProductTransferStrategy {

Product execute();

}

We should be able to call this using a context:

public class ProductStrategyContext {

private ProductTransferStrategy strategy;

public Product execute() {

if (strategy == null) {
throw SomethingExceptionOrHandleDifferently("No strategy set...");
}

return strategy.execute();
}

public void addStrategy(ArticleTransferStrategy strategy) {
this.strategy = strategy;
}
}

Subsequently there are two implemented strategies:

public class CreateProductTransferStrategy implements ProductTransferStrategy {

private final CommandHandler commandHandler;
private final ProductRepository productRepository;

// constructor

@Override
public Product execute() {

Product product = productRepository.findById(recordId);

NewProductCommandChain chain = new NewProductCommandChain(product);

chain.createAndExecute(product, commandHandler);

return commandHandler.execute();

}
public class AdjustDescriptionTransferStrategy implements ProductTransferStrategy {

private final CommandHandler commandHandler;
private final ProductRepository productRepository;

// constructor

@Override
public Product execute() {

Product product = productRepository.findById(recordId);

AdjustDescriptionCommandChain chain = new AdjustDescriptionCommandChain(product);

chain.createAndExecute(product, commandHandler);

return commandHandler.execute();

}

Now let’s create a very generic and dynamic use case. This use case can be used by all processes to come, as a consequence of using the Factory and Strategy design pattern.

public class TransProductDataUseCase implements UseCase<Void, String> {

private final ProductStrategyContext productStrategyContext;
private final ProductFactory productFactory;

// constructor

public Void execute(final String process) {

ProductStrategy strategy = productFactory.get(process);

productStrategyContext
.addStrategy(strategy)
.execute();

}

}

Where the strategy is returned by the Factory:

public class ProductFactory {

private final ProductRepository productRepository;
private final CommandHandler commandHandler;

// constructor

public ProductStrategy get(final String process) {

if ("NEW_PRODUCT".equals(process)) {
return new NewProductStrategy(commandHandler, productRepository);
}

if ("ADJUST_DESCRIPTION".equals(prodcess)) {
return new AdjustDescriptionStrategy(commandHandler, productRepository);
}

return null;

}
}

Let’s summarize what has happened and the benefits of this approach.

We’ve created a strategy design pattern to construct a given structure for each process, with each having a unique implementation: Its own chain of commands that do the mapping, but anything can differ per strategy. The class structure still remains the same.

Subsequently, a use case that is able to determine which strategy to execute given the process name provided, for example by the workflow that is processing these messages.

Finally, the extension class is a controller that simply delegates:

public class DataTransferProductScript extends ScriptTaskBean { 

// data context
private final String process;
private final String recordId;

@Override
public void executeScript() {

Adaptation dataSet = getDataSet();
CommandHandler commandHandler = new CommandHandler();
ProductRepository productRepository = new ProductRepository(dataSet);
ProductFactory productFactory = new ProductFactory(commandHandler, productRepository);
TransferPersonDataUseCase useCase =
new TransferPersonDataUseCase(recordId);

useCase.execute(process); // * check note

}

private Adaptation getDataSet() {
// implement
}


// getters and setters for injected data context

}

*Feel free to play around with the constructor and method-arguments to what suits your implementation the best.

Understand for mapping you’ll need a source and a target. I left that out of the example, the structure remains the same anyhow.

Time to consider the benefits:

  1. Code is decoupled: Each task is assigned to another class: Strategies to execute the complete picture per process. Factory to determine which strategy to execute. Commands to actually do the mapping (not in this example, but already given in the section above) and a command chain that contains all the required commands to do the actual mapping for that given process (strategy).
  2. Code is re-usable. You can interchange your command easily between command chains, and thus processes (strategies).
  3. Code is small: In general code is very specific for a given task. Code is readable!
  4. Code is testable: As you have written code in small pieces you are able to unit test your code. That is definitely a must on enterprise level. Another article on that soon.
  5. Productivity increases! A new process? You only have to write a new strategy with the given commands and chain. Not a new landing area, not a new code structure, not a new workflow.

Conclusion

To improve code quality applying Design Patterns is very useful. It increases separation of concerns, maintainability and productivity. Colleagues understand by the patterns what is happening where, so can easily skip certain parts of code if they are looking for something specific. Recognizing the patterns allows to quickly build more complex structures and maintain it as a team.

--

--