Create Business Logic in CUBA

One of the first questions when developing a CUBA application is: Where should I put my business logic? This guide will explain the options available and their pros and cons.

What Will Be Build

This guide enhances the CUBA petclinic example to show where business logic can be placed in a CUBA application. Mainly discounts are calculated for Pets based on the total amount of their visits in the Petclinic.

Requirements

Your development environment requires to contain the following:

Download and unzip the source repository for this guide, or clone it using git:

Example: CUBA petclinic

The project that is the basis for this example is CUBA Petclinic. It is based on the commonly known Spring Petclinic. The CUBA Petclinic application deals with the domain of a Pet clinic and the associated business workflows to manage a pet clinic.

The underlying domain model for the application looks like this:

Domain model

The main entities are Pet and Visit. A Pet is visiting the petclinic and during this Visit a Vet is taking care of it. A Pet belongs to an Owner, which can hold multiple pets. The visit describes the act of a pet visiting the clinic with the help of its owner.

Business Logic in Controllers

Assuming the discount calculation should be executed when a user clicks a button on the Pets’s browser screen, the most straightforward way to accomplish this is to put the calculation logic right in the associated controller class.

See the Calculate discount button in the petclinic application and the screen controller implementation: PetBrowse.

PetBrowse.java
@UiController("petclinic_Pet.browse")
@UiDescriptor("pet-browse.xml")
@LookupComponent("petsTable")
@LoadDataBeforeShow
public class PetBrowse extends StandardLookup<Pet> {

    @Inject
    private Notifications notifications;

    @Inject
    private Metadata metadata;

    @Inject
    private GroupTable<Pet> petsTable;

    @Subscribe("petsTable.calculateDiscount")
    public void calculateDiscount(Action.ActionPerformedEvent actionPerformedEvent) {

        Pet pet = petsTable.getSingleSelected();

        int discount = calculateDiscount(pet);

        showDiscountCalculatedNotification(pet, discount);
    }

    private int calculateDiscount(Pet pet) {
        int discount = 0;

        int visitAmount = pet.getVisits().size();
        if (visitAmount > 10) {
            discount = 10;
        } else if (visitAmount > 5) {
            discount = 5;
        }
        return discount;
    }

    private void showDiscountCalculatedNotification(Pet pet, int discount) {

        String petName = metadata.getTools().getInstanceName(pet);

        String discountMessage = "Discount for " + petName + ": " + discount + "%";

        notifications.create()
                .setCaption(discountMessage)
                .setType(Notifications.NotificationType.TRAY)
                .show();
    }
}
This approach is acceptable if the logic is invoked from a single point and it is not too complex to fit into a couple of short methods.

Client Tier Beans

The in-controller solution works as long as the logic should only be executed within one screen. Once the calculation should be executed in multiple UI screens there is a better way not to duplicate the code. The logic can be extracted to a common place available for both controllers. This option is using a Spring bean in the client tier.

A managed bean is a class annotated with @Component annotation. It can be injected via @Inject into other beans and screen controllers. If a bean has a separate interface, it can be accessed through the interface instead of the class.

class diagramm using client tier beans

Please note that in order to be accessible for screen controllers, the bean must be located in global, gui or web modules of the application. In the former case the bean will be also accessible for the middleware.

The example that is implemented as (multiple) client tier beans is to display the contact information of the pet’s owner directly from the pet browser or the pet details screen.

For this there are two Spring beans:

PetContactFetcherBean.java
@Component(PetContactFetcher.NAME)
public class PetContactFetcherBean implements PetContactFetcher {

    @Inject
    private DataManager dataManager;

    @Inject
    private Messages messages;

    @Override
    public Optional<Contact> findContact(Pet pet) {

        Optional<Owner> petOwner = loadOwnerFor(pet);

        if (petOwner.isPresent()) {

            Owner owner = petOwner.get();
            String telephone = owner.getTelephone();
            String email = owner.getEmail();
            String address = formatOwnerAddress(owner);

            if (isAvailable(telephone)) {
                return createContact(telephone, ContactType.TELEPHONE);
            } else if (isAvailable(email)) {
                return createContact(email, ContactType.EMAIL);
            } else if (isAvailable(address)) {
                return createContact(address, ContactType.ADDRESS);
            } else {
                return Optional.empty();
            }
        } else {
            return Optional.empty();
        }
    }

    //...
}

The beans are used in the Pet browse screen and the Pet details screen. The usage looks like this:

PetEdit.java
@UiController("petclinic_Pet.edit")
@UiDescriptor("pet-edit.xml")
@EditedEntityContainer("petCt")
@LoadDataBeforeShow
public class PetEdit extends StandardEditor<Pet> {

    @Inject
    private PetContactFetcher petContactFetcher;

    @Inject
    private PetContactDisplay petContactDisplay;

    @Subscribe("fetchContact")
    public void fetchContact() {

        Pet pet = getEditedEntity();

        Optional<Contact> contactInformation = petContactFetcher.findContact(pet);

        petContactDisplay.displayContact(contactInformation, this);
    }
}

The benefit compared to the first in-controller solution is that the code-reuse is higher. It is possible to use the logic in different places.

PetContactDisplay is a bean in the web module, therefore it is only available within the client tier. PetContactFetcher, on the other hand, is a bean in the global module which means that it is also possible to use the logic in the middleware as well as the client tier.

Middleware Services

The next approach on where to put the business logic is a middleware service. The service is the most appropriate place for business logic, because it achieves the following goals:

  • The business logic will be available for all types of clients including the Polymer UI.

  • APIs that are available only on the middleware: EntityManager, Transaction, etc. are accessible to the business logic

class diagramm using services

In order to invoke the middleware business logic from the client, a service needs to be created. CUBA Studio streamlines the creation process of a service:

  • Select the Middleware section in the CUBA project tree and click New > Service in the context menu.

  • Change the service interface name to DiseaseWarningMailingService. The bean class and service names will be changed accordingly. Click OK.

  • Open the service interface in the editor. Create a method and implement it in the service class.

In the Petclinic application, the following service was created: DiseaseWarningMailingService. It sends out disease warning mailings to the potentially endangered pets' owners.

The implementation of a service looks like this in case of DiseaseWarningMailingService:

DiseaseWarningMailingServiceBean.java
@Service(DiseaseWarningMailingService.NAME) (1)
public class DiseaseWarningMailingServiceBean implements DiseaseWarningMailingService {

    @Inject
    private DataManager dataManager;

    @Inject
    private EmailerAPI emailerAPI;

    @Override
    public int warnAboutDisease(PetType petType, String disease, String city) { (2)

        List<Pet> petsInDiseaseCity = findPetsInDiseaseCity(petType, city);

        List<Pet> petsWithEmail = filterPetsWithValidOwnersEmail(petsInDiseaseCity);

        petsWithEmail.forEach(pet -> sendEmailToPetsOwner(pet, disease, city));

        return petsWithEmail.size();
    }

    // ...

}
1 Declaration of DiseaseWarningMailingServiceBean as the Middleware Service implementation
2 Implementation of the business logic interface

The UI controller usage is very similar to the invocation of a client tier bean. The CreateDiseaseWarningMailing screen controller looks like this:

CreateDiseaseWarningMailing.java
@UiController
@UiDescriptor("create-disease-warning-mailing.xml")
public class CreateDiseaseWarningMailing extends Screen {

    @Inject
    private DiseaseWarningMailingService diseaseWarningMailingService; (1)

    @Inject
    private Notifications notifications;

    @Subscribe("createDiseaseWarningMailing")
    protected void createDiseaseWarningMailing(Action.ActionPerformedEvent event) {

        int endangeredPets = diseaseWarningMailingService.warnAboutDisease(petType.getValue(),
                disease.getValue(), city.getValue()); (2)

        closeWithCommit().then(() ->
                notifications.create()
                        .setCaption(endangeredPets + " Owner(s) of endangered Pets have been notified")
                        .setType(Notifications.NotificationType.TRAY)
                        .show()
        );
    }
}
1 Injection of the middleware service via its interface definition
2 Invocation of the business logic in the controller action

With that middleware service in place, the logic of creating disease warning mailings can now be shared across UI screens in the web tier. Additionally any other middleware code can execute the logic, since it is not tied to the web tier anymore. Furthermore, it is possible to trigger it via the REST API and with that to expose it also to other UI technologies like the Polymer UI.

Summary

There are several options when it comes to where to put the business logic in a CUBA application. A controller can be a good and easy start, although it has some downsides when it comes to sharing the code. A client bean solves this problem to some degree. Middleware services allow for sharing business logic across different clients (e.g. Polymer Client) and is the most appropriate way of putting business logic.

Additionally, there are several advanced techniques like JMX Beans or Entity Listeners that also allow for certain business logic to be executed in particular scenarios. The decision where to put the business logic for a given case should be done on a case to case basis. But the mentioned main places of Controllers & Spring Beans / Spring Services for placing business logic are a good starting point.