Extending Domain Model
The Self-Managed Commerce platform is built on a flexible e-commerce domain object model. If your organization has specific needs, you can extend it. Changes to the domain object model affect the platform on many levels, both in terms of functionality and performance. Before making domain changes, you need to carefully consider the impact.
Be aware of how Load tuners and FetchGroups optimize the loading of fields for different types of objects
If you are thinking about extending catalog-related domain objects (
Product,Catalog, etc.), consider whether you can use attributes instead
The basic steps for extending the domain object model are:
- Modifying the database schema
- Creating the domain class and interface
- Modifying the ORM configuration
- Add references to the Persistence Unit
- Registering new domain classes with the BeanRegistrar
Optional steps you may need are:
Modifying the database schema
When creating or modifying a domain object, you need to modify the schema to define the new fields.
If you are creating a completely new domain object, you will need to add a new table.
If you’re extending an existing domain object, we recommended that you use the single table inheritance strategy. With this strategy, the base class’s data is stored in the base table and your extended class’s data is stored in the same table. To implement this, you need to:
- Modify the table to define the new fields.
- Modify the table to define a discriminator field. Usually this is a column named
TYPEwith a data type ofVARCHAR(50).
note
This discriminator column is used by OpenJPA to identify, for each row, which domain object to instantiate when retrieving the data.
Creating the domain class and interface
Any new or extended domain object has both an interface defining the methods and a class containing the implementation. To ensure the domain object's data is persisted and loaded correctly, the implementation class needs to contain the appropriate JPA annotations.
Choosing the base interface and abstract class
When creating a new domain object, the domain interface should extend one of the platform's persistence interfaces and the implementation class should extend the corresponding abstract class. The following class hierarchy is available:
Persistable (interface)
└─ AbstractPersistableImpl
├─ Entity (interface, extends Persistable + GloballyIdentifiable + Initializable)
│ └─ AbstractEntityImpl
│ └─ AbstractLegacyEntityImpl
│ └─ AbstractListenableEntityImpl
│
└─ AbstractLegacyPersistenceImpl
Persistable / AbstractPersistableImpl
Use when the domain object only requires a database-assigned unique identifier (UIDPK) and does not need a GUID.
- Interface:
com.elasticpath.persistence.api.Persistable - Abstract class:
com.elasticpath.persistence.api.AbstractPersistableImpl
Provides:
getUidPk()/setUidPk(long)— System-dependent unique identifier assigned by the database.isPersisted()— ReturnstruewhenUIDPK > 0.
Choose this when: You are creating a simple value object or child entity that is cascade-loaded through a parent and does not need to be independently identified across environments.
Entity / AbstractEntityImpl
Use when the domain object needs both a database-assigned UIDPK and an environment-independent GUID (globally unique identifier).
- Interface:
com.elasticpath.persistence.api.Entity(extendsPersistable,GloballyIdentifiable,Initializable) - Abstract class:
com.elasticpath.persistence.api.AbstractEntityImpl
Provides everything from AbstractPersistableImpl, plus:
getGuid()/setGuid(String)— Environment-independent identifier (fromGloballyIdentifiable).initialize()— Deferred initialization hook called by the bean factory for new instances. Auto-generates the GUID.equals()/hashCode()— Implemented based onGUID, ensuring consistent identity across staging and production environments.
Choose this when: You are creating a new top-level domain entity that must be identifiable across environments (e.g., for data sync or import/export) and does not need bean factory access or property change listeners within the domain object itself. This is the recommended base class for new domain objects.
AbstractLegacyEntityImpl
Extends AbstractEntityImpl and adds the ability to access the Spring bean factory from within the domain object.
- Interface: Domain interface should extend
Entity. - Abstract class:
com.elasticpath.domain.impl.AbstractLegacyEntityImpl
Provides everything from AbstractEntityImpl, plus:
getPrototypeBean(String, Class)/getSingletonBean(String, Class)— Access to Spring-managed beans from within the domain object.
Choose this when: The domain object's internal logic needs to look up other Spring beans at runtime. This is a legacy pattern — prefer injecting dependencies through services rather than looking them up from within domain objects.
AbstractListenableEntityImpl
Extends AbstractLegacyEntityImpl and adds PropertyChangeListener support.
- Interface: Domain interface should extend
EntityandListenableObject(com.elasticpath.domain.ListenableObject). - Abstract class:
com.elasticpath.domain.impl.AbstractListenableEntityImpl
Provides everything from AbstractLegacyEntityImpl, plus:
addPropertyChangeListener()/removePropertyChangeListener()— Register listeners that are notified when properties change.firePropertyChange(String, Object, Object)— Protected method to trigger change notifications from setter methods.
Choose this when: Other parts of the application need to observe and react to property changes on the domain object. Many existing core entities (e.g., ProductImpl, CategoryImpl) extend this class.
AbstractLegacyPersistenceImpl
Extends AbstractPersistableImpl (not AbstractEntityImpl) and adds bean factory access. This is a separate branch of the hierarchy that provides UIDPK and bean factory access without GUID support.
- Interface: Domain interface should extend
PersistableandInitializable(com.elasticpath.base.Initializable). - Abstract class:
com.elasticpath.domain.impl.AbstractLegacyPersistenceImpl
Provides everything from AbstractPersistableImpl, plus:
getPrototypeBean(String, Class)/getSingletonBean(String, Class)— Access to Spring-managed beans from within the domain object.initialize()— Deferred initialization hook.
Choose this when: The domain object is a child or value object that does not need a GUID but does need to access the bean factory from within its implementation. Like AbstractLegacyEntityImpl, this is a legacy pattern.
Summary
| Base class | Interface to extend | UIDPK | GUID | Bean factory access | Property change listeners |
|---|---|---|---|---|---|
AbstractPersistableImpl | Persistable | Yes | No | No | No |
AbstractEntityImpl | Entity | Yes | Yes | No | No |
AbstractLegacyEntityImpl | Entity | Yes | Yes | Yes | No |
AbstractListenableEntityImpl | Entity, ListenableObject | Yes | Yes | Yes | Yes |
AbstractLegacyPersistenceImpl | Persistable, Initializable | Yes | No | Yes | No |
tip
For new domain objects, prefer AbstractEntityImpl / Entity unless you have a specific need for bean factory access or property change listeners. The AbstractLegacy* classes exist primarily for backward compatibility with existing domain objects.
JPA annotations
At the class level, include the following annotations:
@Entityand@Table- Identifies the object as an Entity, which is an object that can be persisted in the system.@DiscriminatorValue- Required for an extended domain object. Identifies a value that will be stored in the base table’s discriminator column. The value differentiates the domain objects and the extension objects in their common database table.@DataCache- Extended domain objects do not automatically inherit data cache settings. If the parent class specifies data cache settings, the extended class must also include a matching annotation.@ShopperReadOnly- Identifies if the entity should be protected from modification by shoppers. If specified, any attempt by Cortex to insert, update, or delete a record of this type will cause anEpSystemExceptionto be thrown.
At the method level, include the following annotations:
@Basicand@Columnannotations on the getter methods to specify how field data is mapped to table columns.- Annotation required to load data from related tables, such as
@OneToMany.
The following code snippet shows the table annotations for an extension of the OrderImpl class.
@Entity
@Table(name = OrderImpl.TABLE_NAME)
@DiscriminatorValue("ExtOrderImpl")
@DataCache(enabled = false)
public class ExtOrderImpl extends OrderImpl implements ExtOrder {
/** Serial version id. */
private static final long serialVersionUID = 5000000002L;
}
For more information, see the OpenJPA documentation on metadata.
Modifying the ORM (Object Relational Mapping) configuration
If you’re extending an existing domain class, JPA needs to be made aware that the base class accepts extensions. This means overriding the JPA metadata for the base class.
To do this, open the extensions/core/ext-core/src/main/resources/META-INF folder and add a new metadata ORM file.
For example, to make OrderImpl extensible, create ext-order-orm.xml and populate it with the following:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" version="2.0">
<entity class="com.elasticpath.domain.order.impl.OrderImpl">
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-value>OrderImpl</discriminator-value>
<discriminator-column name="TYPE" discriminator-type="STRING"/>
</entity>
</entity-mappings>
Add references to the Persistence Unit
New persistence classes and metadata ORM files should be registered in the core extension’s jpa-persistence.xml.
For example, to register the ext-order-orm.xml file, open extensions/core/ext-core/src/main/resources/META-INF/jpa-persistence.xml and add a reference to the new metadata ORM file within the persistence-unit node as follows:
<mapping-file>META-INF/ext-order-orm.xml</mapping-file>
To register ExtOrderImpl, open extensions/core/ext-core/src/main/resources/META-INF/jpa-persistence.xml and add a reference to the extended domain class within the persistence-unit node as follows:
<class>com.elasticpath.extensions.domain.order.impl.ExtOrderImpl</class>
Registering new domain classes with BeanRegister
New domain classes must be registered through the core bean factories. You can add new bean definitions in the core extension’s ep-core-plugin.xml to include entries for the new classes.
For example, to register the extended domain class, open extensions/core/ext-core/src/main/resources/META-INF/conf/ep-core-plugin.xml, and add the following:
<bean id="order" scope="prototype" class="com.elasticpath.extensions.domain.order.impl.ExtOrderImpl"/>
After you make this change, running mvn clean install from the command line against the core extension project will compile your classes, perform bytecode enhancement, rebuild the jar, and install it in your repository.
Exclude references from the Persistence Unit
To exclude existing domain classes and/or metadata ORM files, you can add to the exclusion lists persistenceMappingExcludedClasses and persistenceMappingExcludedFiles respectively. These can be found in the core extension’s ep-core-plugin.xml file.
Override Persistence Unit Properties
Occasionally you may wish to override some of the properties defined in the core persistence unit, or add new properties. To do so, add the properties to the persistencePropertyOverrides map in the core extension’s ep-core-plugin.xml file. Do not add or change properties in any of the jpa-persistence.xml files, as the order in which these files are resolved cannot be guaranteed.