Skip to main content

Unit 12: Implement Domain Service

๐ŸŽฏOverview

In this course you will learn how to implement a service within the domain namespace using your local IDE.

Outlineโ€‹

For our next implementation we will stay in the domain namespace and continue with coding a domain service. As explained in Unit 4: Design Domain Service and Business Error - contrary to a command - a domain service performs logic that is independent of a particular instance and can be used to orchestrate several commands. Furthermore, domain services can have an input and an output entity and are able to publish events and throw business errors.
Since services are part of the whole domain and do not belong to a root entity, all service classes are located in the service folder of the domain. The design of domain services modeled in the Solution Designer is reflected in the service classes by its fields, method signatures and available scope.

In general, the implementation of domain services does follow a pattern like for commands, but the following pieces may be part of it:

  • Retrieve the service input.
  • Manipulate multiple entites either directly or via orchestrating commands for them. If something goes wrong or a condition is not met, throw a business error.
  • Publish a business event.
  • Return or assign the service output.

Prerequisitesโ€‹

โ—๏ธAlternative starting point for your training

If you rather want to use this course as a starting point of your training, you can use a different asset where all courses for designing are already completed.
In this case - depending on the chosen implementation language - use either asset "Order_Java_Code_0.1" or "Order_TypeScript_Code_0.1" to create a new project and to continue your training.
You can look up how to create a new project from the Order assets in the Preparation section in the Course Introduction.

Exerciseโ€‹

Estimated time: 15 minutes
Exercise goal: After completing this course you are able to apply all pieces of implementing a domain service.
Supported languages: Java and TypeScript

In this exercise we will implement a domain service CancelAllOrdersOfCustomer which cancels all orders of a specific customer that have not been processed yet. Since the service is only responsible for customer orders, the cancellation will only affect CustomerOrder root entities.

Step 1: Implement the domain serviceโ€‹

Our domain service to cancel all orders of a customer will include the following:

  • Extract all orders with the provided customer reference id that have status "OPEN" or "IN_PROGRESS".
  • If no matching orders were found, throw a Business Error.
  • Call the CancelOrder command for each of the matching orders.
  1. Open the file /src/main/java/<package-name>/domain/ord/service/CancelAllOrdersOfCustomer.java.
    You will see an auto-generated stub in which you can start your implementation.

    <package-name>: The package name of the Java project (e.g. com.knowis.orderjfinal)

    @Service("ord_CancelAllOrdersOfCustomer")
    public class CancelAllOrdersOfCustomer extends CancelAllOrdersOfCustomerBase {

    private static final Logger log = LoggerFactory.getLogger(CancelAllOrdersOfCustomer.class);

    public CancelAllOrdersOfCustomer(DomainEntityBuilder entityBuilder, Repository repo) {
    super(entityBuilder, repo);
    }

    @NewSpan
    @Override
    public void execute(
    CancelAllOrdersOfCustomerInput cancelAllOrdersOfCustomerInput
    ) throws NoMatchingOrdersFound {
    log.info("CancelAllOrdersOfCustomer.execute()");
    // TODO: Add your service implementation logic

    }
    }
  2. To reuse the implemented command CancelOrder as well as the CustomOrderRepository class we added manually in preparation for the implementation phase, we have to create a private field for class OrderCommand and for the repository class. Additionally we have to add two constructor parameters that are then assigned to the respective private fields.

    private final CustomOrderRepository customOrderRepo;
    private final OrderCommand orderCommand;

    public CancelAllOrdersOfCustomer(
    DomainEntityBuilder entityBuilder,
    Repository repo,
    CustomOrderRepository customOrderRepo,
    OrderCommand orderCommand
    ) {
    super(entityBuilder, repo);
    this.customOrderRepo = customOrderRepo;
    this.orderCommand = orderCommand;
    }

    Please use the auto-import function of your IDE to import the missing classes (and repeat this for the following steps). If you are not very familiar with imports, go back to Course 11 - Step 3: Import missing Classes for detailed information on class imports. Furthermore, you are free to compare your current imports with the recommended list of imports at the end of this section. Therefore, click here.

  3. After injecting the fields, we will extend the execute method in the following steps.
    First, we will extract all orders with the provided customer reference id that have status "OPEN" or "IN_PROGRESS". Therefore we will use the CustomOrderRepository class.

    // Step 1: Find all customer orders matching the referenceId and status OPEN or IN_PROGRESS
    // Therefore use the filter function from the manually provided class CustomOrderRepository
    List<CustomerOrder> matchingOrders = this.customOrderRepo.findCustomerOrdersByStatus(
    cancelAllOrdersOfCustomerInput.getCustomerReferenceId(),
    List.of(Status.OPEN, Status.IN_PROGRESS)
    );
    โ—๏ธinfo

    If you want a detailed description on how to extend repository classes for advanced filtering on your own, check out How-To: Extend MongoDB Repositories for advanced filtering.

  4. If no matching orders exist, we will throw a business error.

    // Step 2: If no matching orders found (empty list) throw a business error
    if (matchingOrders.isEmpty()) {
    throw new NoMatchingOrdersFound();
    }
  5. After this, we have to loop over all matching orders and use the OrderCommand class to cancel them one by one.

    // Step 3: For all matching orders execute the 'CancelOrder' command
    for (CustomerOrder order : matchingOrders) {
    try {
    this.orderCommand.cancelOrder(order);
    } catch (OrderCannotBeCancelled e) {
    log.error(
    TextFormatter.format(
    "Cannot cancel order of customer reference ID = {}, order ID = {}",
    cancelAllOrdersOfCustomerInput.getCustomerReferenceId(),
    order.getId()
    ),
    e
    );
    }
    }
โ„น๏ธRecommended List of Imports

The provided list supports you to compare your current imports with the recommended list, especially if your IDE complains about errors in the code at this stage of the course.

List of Imports - File CancelAllOrdersOfCustomer.java
// Fundamental 1: Generated classes based on design
import com.knowis.orderjfinal.sdk.domain.facade.DomainEntityBuilder;
import com.knowis.orderjfinal.sdk.domain.facade.Repository;
import com.knowis.orderjfinal.sdk.domain.ord.entity.CustomerOrder;
import com.knowis.orderjfinal.sdk.domain.ord.error.NoMatchingOrdersFound;
import com.knowis.orderjfinal.sdk.domain.ord.error.OrderCannotBeCancelled;
import com.knowis.orderjfinal.sdk.domain.ord.service.CancelAllOrdersOfCustomerBase;
import com.knowis.orderjfinal.sdk.domain.ord.type.Status;

// Fundamental 2: Generated classes reflecting the capabilities of the Workbench
import k5.sdk.springboot.util.text.TextFormatter;

// Fundamental 3: Built-in Java classes
import io.micrometer.tracing.annotation.NewSpan;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.List;

// Fundamental 4: Classes from other implementation files
import com.knowis.orderjfinal.domain.ord.command.OrderCommand;
import com.knowis.orderjfinal.domain.ord.repository.CustomOrderRepository;

The package name of the provided code is com.knowis.orderjfinal. All occurrences of this phrase have to be replaced with the package name of your project.

๐Ÿ’กDefining the service output

For this service we have not defined an output entity, so we stop with the implementation here. But if an output is defined in your own domain service, this is simply applied in implementation by a return-statement.

๐ŸŒŸCongratulations!

You have successfully implemented a domain service! In combination with the design in the Solution Designer, you are able to use domain services in your own application for various purposes, as they are not bound to a specific instance of a root entity.

What's Next?โ€‹

In the next course you will learn how to implement the agent that gets triggered when the status of a customer changes.