Elastic Path Commerce Development

Domain layer

Domain layer

The domain layer contains an object model of the ecommerce domain. This object model consists of classes that model real-world entities such as customers and products. The behavior and relationships of these classes should be a reflection of the real-world entities. For example, customers have collections of addresses and products have references to price objects. As much as possible, domain objects should encapsulate their behavior so that the objects who collaborate with them are unaware of internal implementation details. Furthermore, domain objects should be kept relatively free from the constraints of frameworks, persistence, etc. so that they are a relatively pure expression of the domain.

The following sections cover key topics and design considerations in domain model development.

Key domain model interfaces

The following interfaces help define domain objects:

  • GloballyIdentifiable - identifies an object that has a global identity field (GUID).
  • Initializable - Implemented by domain objects that need to be able to init values for new instances separate from the default constructor.
  • Persistable - Represents objects that can be persisted
  • Entity - combines GloballyIdentifable, Initializable and Persistable. The majority of stored domain objects in Elastic Path are Entity objects
  • EpDomain - defines a non-stored domain object - combines Serializable and Initializable.

Typically when creating a new domain object you choose one of the abstract classes provided that implement the above - typically AbstractEntityImpl for uniquely identified objects that will be stored in a database, AbstractPersistableImpl for objects to be stored in the database that don't require a unique identifier (typically these are objects that are leaf nodes in a domain object graph rather than root objects), and AbstractEpDomainImpl for transient domain objects that require internal access the to the bean factory. There are also AbstractLegacyEntityImpl and AbstractLegacyPersitenceImpl classes that provide access to the bean factory for persistable objects.

How to create new instances of domain objects

In Elastic Path, domain objects can be instantiated by calling the BeanFactory's getBean() method. (See the coreBeanFactory bean definition in conf/spring/models/domainModel.xml.) Many of the bean names that can be used in the bean name parameter are constants defined in ContextIdNames.java.

// Create a new instance of Customer
Customer myCustomer = (Customer) coreBeanFactory.getBean(ContextIdNames.CUSTOMER);

If you are adding a new domain object to the system, create a standard Spring bean definition and make sure it is defined with scope="prototype". Most prototype bean definitions can be found in conf/spring/prototypes/prototypes.xml

In some cases, you will need to declare multiple domain objects that are initialized with different property values. In this case, add the bean definition to domainModel.xml. Note that this way of declaring instances is used for all singleton service objects.

Domain object identity - UIDPK vs GUID

Domain objects in Elastic Path have two kinds of identifiers, a UIDPK and a GUID.

The UIDPK is a surrogate key which is generated by the OpenJPA table strategy automatically when a record is added to a table. After it is created, its value cannot be changed. In database tables, UID_PK is a unique primary key.

GUID is the acronym for Globally Unique IDentifier. In Elastic Path it is used as the general name for the identifier of an entity object. In most cases, the GUID is the natural key of an entity object. For example, the natural key of ProductSku is its SKU code, so the SKU code is the GUID for ProductSku objects. In other words, you can think of GUID as a generic name for identifier of entity object. However, some specific entities may have their own name for the same identifier, such as "SKU code."

The following table provides more comparison between the UIDPK and the GUID.

UIDPK GUID COMMENTS

TYPE

Integer

String

LENGTH

32 bit or 64 bit

255 byte (maximum)

SCOPE

One system

Multiple systems

The same product might have differenct UIDPK in the staging and production database, but they will always have the same GUID.

USAGE

Entity object or value object

Entity object

UIDPK is used to identify an entity or a value object in one system. It's also used in associations(foreign key, etc.) between entities and value objects. GUID is only used to identify an entity. It can be used from in CRUD(create, retrieve, update & delete) operations on an entity from other systems (e.g. web services and the import manager).

GENERATION

Automatically

Hybrid

You can call the setGuid() method to manually set a GUID. If you don't manually set one, the GUID is assigned when you create a new entity. The default behavior is to allow the GUID to be automatically assigned. Unlike the UIDPK, a GUID can be changed after creation.

ALIASES

N/A

Can have aliases for different entity objects

Examples: ORDER GUID is also called ORDER NUMBER SKU GUID is also called SKU CODE PRODUCT GUID is also called PRODUCT CODE CATEGORY GUID is also called CATEGORY CODE ATTRIUBTE GUID is also called ATTRIBUTE KEY

Bi-Directional Relationships

Avoid creating bi-directional relationships between parent objects and the child objects that they aggregate using a collection class. By avoiding bi-directional relationships we eliminate the complexity of maintaining the parent link when a child is added or removed from the collection. In some cases the bi-directional relationship cannot be avoided. For example, ProductSku references Product because it must fall back to the product's price when a client requests a price from the SKU but no price has been defined at the SKU level.

Domain Object Serialization

Generally, domain objects should be made serializable because they might be replicated from one application server to another in a clustered application server environment. To make a domain object serializable, it must implement the "Serializable" interface and all of its aggregated fields must be serializable.

public class FrequencyAndRecurringPrice implements Serializable {
    /**
     * Serial version id.
     */
    public static final long serialVersionUID = 5000000001L;
    
    private final Quantity frequency;
    private Money amount;
    private final String name;
  ...
}

The Persistable, Entity and EpDomain interfaces already extend Serializable.

Setting default field values for domain objects

There are three ways to set default values for domain object fields.

  • Set values in the domain object constructor - This technique is seldom used because the field initialization can no longer be controlled.
  • Field initializer declaration - This may be used for fields whose default values are cheap to create.
  • Make sure the object implements Initializable and set the values in the initialize() method. This is the preferred technique. Spring will call the initialize method during bean instantiation (thanks to InitializableBeanPostProcessor).

Using initialize() is preferred because it can be used to control when default values are initialized. In production, it is wasteful to set expensive default values when creating new domain objects because they will typically be overwritten by the persistence layer immediately. For example, Maps consume a lot of memory while computing fields like GUIDs and dates are CPU intensive. When running JUnit tests, however, we will need to set the default values so that the functionality can be tested without throwing NullPointerExceptions.

When setting default values in initialize(), check that the value has not yet been set before initializing fields.

@Override
public void initialize() {
  super.initialize();
  if (this.startDate == null) {
    this.startDate = new Date();
  }
  if (productCategories == null) {
    productCategories = new HashSet();
  }
  if (productPrices == null) {
    productPrices = new HashSet();
  }
  if (promotionPrices == null) {
    promotionPrices = new HashMap();
  }
  if (localeDependantFieldsMap == null) {
    localeDependantFieldsMap = new HashMap();
  }
  if (productSkus == null) {
    productSkus = new HashMap();
  }
}