Working with the Domain Driven Architecture

From Opentaps Wiki
Jump to navigationJump to search

Here are some tips for working with the Domain Driven Architecture:

Instantiating Services and Repositories

If you have a domain, the easiest way to instantiate a Service or Repository from your domain is to use the instantiateService or instantiateRepository methods, like this:

    public OrderInventoryService getOrderInventoryService() throws ServiceException {
        return instantiateService(OrderInventoryService.class);

This will automatically set up the Infrastructure and User for your Service or Repository.

Documenting your Services

Since your domain Services do not have to be routed through the ofbiz service engine, you should put the documentation in the Java method so that they would show up in the Java docs as well. For example:

    * Adds a <code>LockboxBatchItemDetail</code> to an existing
    * @throws ServiceException if an error occurs
    * @see #setLockboxBatchId required input <code>lockboxBatchId</code>
    * @see #setItemSeqId required input <code>itemSeqId</code>
    * @see #setAmountToApply required input <code>amountToApply</code>
    * @see #setCashDiscount required input <code>cashDiscount</code>
    * @see #setPartyId optional input <code>partyId</code>
    * @see #setInvoiceId optional input <code>invoiceId</code>

Messages from Your Services

Service can return success and error messages to the user.

To return an error message simply throw a ServiceException.

To return a success message, you can use either setSuccessMessage or addSuccessMessage. The former is mean to return a single message whereas the later allows you to return a list of messages.

Each method supports localization by using labels that are translated into the user locale, refer to the API for more details.

For example:

public void serviceA() throws ServiceException {
    try {
        // perform some modifications ...
        if (some error) {
            // return an error label, the second arguemnt is the context for substitution in the message
            throw new ServiceException("ErrorLabel", null);
    } catch (SomeException e) {
        // generic error that you do not handle are also displayed back to the user
        // some exception are already localized (for example the RepositoryException related to not found entities)
        throw new ServiceException(e);

Beware that nested direct service calls can override the success message, if you do not want that be sure to set it as the last instruction of the method.

public void serviceB() throws ServiceException {
    // perform some modifications ...

public void serviceA() throws ServiceException {
    // perform some modifications ...
    // SuccessLabelB has overwritten the success message

If you need to return a list of messages use addSuccessMessage, and beware that setSuccessMessage will overwrite the list.

public void serviceB() throws ServiceException {
    // perform some modifications ...

public void serviceA() throws ServiceException {
    // perform some modifications ...
    // returns [SuccessLabelA, SuccessLabelB]

Working with Entity Field Names

Each base entity has an enumeration listing its fields, and this enumeration is used in the framework API instead of using string literals. This means it is possible to use the IDE autocompletion but also that the resulting code is no longer at risk of resulting in runtime errors due to typos.

For example, when writing condition maps for the repository find methods, the repository provides a map method which allows you to write the map like this:

findOne(Order.class, map(Order.Fields.orderId, "WS10000"));

findList(OrderItemAssoc.class, map(OrderItemAssoc.Fields.orderId, "WS10000", OrderItemAssoc.Fields.orderItemSeqId, "00001"));

It is also used when retrieving distinct field values from a list, like for example:

List<OrderItem> items = findList(OrderItem.class, map(OrderItem.Fields.orderId, "WS10000"));
Set productIdsInOrder = Entity.getDistinctFieldValues(items, OrderItem.Fields.productId);

Extending the Domains Directory

opentaps 1.5 and later give you the ability to extend the domains directory, so you can add additional domains or implement your own domains directory which overrides the default ones from opentaps. This can be helpful if your components want to use custom domains or implement additional domains which are not listed in the main opentaps domains directory.

To do this, implement your own DomainsLoader which extends the main

public class AlternateDomainsLoader extends DomainsLoader {

Then, define your own domains directory file. This file could just define additional domains, such as this example from opentaps/opentaps-tests/config/test-domains-directory.xml:

   <bean id="testDomain" class=""/>

Or you can re-define all the domains again, to override the ones from opentaps, like in this example from opentaps/opentaps-tests/config/alternate-domains-directory.xml:

   <bean id="opentapsBillingDomain" class=""/>
   <bean id="opentapsOrderDomain" class="org.opentaps.common.domain.order.OrderDomain"/>
 < !-- etc. etc -->
   <bean id="domainsDirectory" class="org.opentaps.domain.DomainsDirectory">
       <property name="billingDomain"><ref bean="opentapsBillingDomain"/></property>
       <property name="orderDomain"><ref bean="opentapsOrderDomain"/></property>
   < !-- etc. etc. -->

Then, in your custom domains loader's constructor, you can register additional domains directory XML files in the constructor:

	public static String TEST_DOMAINS_DIRECTORY = "test-domains-directory.xml";

	public TestDomainsLoader(Infrastructure infrastructure, User user) {
		super(infrastructure, user);

If you want to switch from the main opentaps domains directory to your own, re-implement the getDomainsDirectory method to point to your domains directory file:

        public static String ALT_DOMAINS_DIRECTORY = "alternate-domains-directory.xml";
	public DomainsDirectory getDomainsDirectory() {
		return super.getDomainsDirectory(ALT_DOMAINS_DIRECTORY);

That's all there is to it. Now you can use the domains loader just as before:

  testDomain = domainsLoader.getDomainsDirectory().getDomain(TestDomainsLoader.TEST_DOMAIN);


  billingDomain = alternateDomainsLoader.getDomainsDirectory().getDomain(DomainsDirectory.BILLING_DOMAIN);

and it will load the domains from your file.

How Does It Work?

The astute reader would have noticed that there is a slight difference in the code above for getting the domains. Instead of using


We are now using


The two are equivalent, but there are two changes in the internal architecture of the domains directory which permits the use of additional domains directory files and adding additional domains. The DomainsLoader now keeps a Map of domains directory file name -> DomainsDirectory, and the DomainsDirectory now keeps a map of name -> Domain. This is why you can now use the getDomain(String domainName) method to get your domain from the domain directory, and why you can add additional domains with a single bean definition.

Similarly, now keeps a Map of domains directory file names and the corresponding DomainsDirectory. The getDomainsDirectory() method returns the domains directory located at the default opentaps domains directory file defined in DomainsLoader.DOMAINS_DIRECTORY_FILE. When you are implementing an alternative domains loader, you are just overriding getDomainsDirectory() with getDomainsDirectory(String domainsDirectoryFile) to load of domains directory in a different file.

IMPORTANT: You must not have two domains directory XML files of the same name, even if they are in different components.

For More Examples

See package for examples of how to implement and use additional domains and alternative domains directories. The domains directory XML files are in opentaps/opentaps-tests/config/