Document Properties
Kbid2307B9
Last Modified15-Mar-2016
Added to KB26-Mar-2012
Public AccessEveryone
StatusOnline
Doc TypeGuidelines, Concepts & Cookbooks
Product
  • ICM 7.6
  • ICM 7.7
  • ICM 7.8
  • ICM 7.9
  • ICM 7.10

Concept - Business Objects

1 Introduction

This page describes the architectural concepts for the business object layer. The business object layer:

  • Defines a stable API that provides business functionality
  • Defines entity objects and their relations with each other
  • Defines the mapping of the entity objects and their attributes and relations to an underlying data/persistence layer
  • Provides the possibility to customize and extend the existing functionality
  • Provides a more object-oriented view on the available business functionality, making for example user interface development much easier
  • Permits easy navigation between business objects

2 Related Document

3 Purpose of the Business Object Layer

The business object layer provides an explicit business-oriented domain model as an API. In a picture that shows the typical architecture layers of a business application, it can be located on top of the persistence layer (or data layer), and below the application layer that forms the actual application from a reusable domain layer - the business object layer.
It forms a new level of abstraction over the persistent state of objects. It comes with concepts to change and extend the behavior of business objects without breaking the CAPI barrier. It hides the underlying internal implementation, which can still be based on the existing ORM model, or on any other back end. It also provides a more object-oriented view on the available business functionality, which, for example, makes user interface development much easier.

Architecture Layers

3.1 Minimal Functional Scope

A business object API should contain methods, which exists in the "real" world. This way a business user easily understands the relation between the object in the application and the real world commerce object.

This section describes a minimal set of functions, which a business object and repository should provide. It is always helpful to think about the real world objects to find proper method and business object names. For example: I would add a product to my cart instead of create a product line item.

3.1.1 Repository Functions

Each repository should provide following methods or functions:

  • create(externalID, mandatoryParameters): BusinessObject
  • getByExternalID(externalID): BusinessObject
  • getByID(internalID):BusinessObject
  • findBy(parameters):List|Collection<BusinessObject>
  • remove(BusinessObject)

These functions can be provided directly or indirectly (like BusinessObject.remove()), with more or less parameters. The externalID can be replaced by other unique key-names inside of the repository like "name", "sku" ....

Methods to reference to external business objects can be created via the business object itself or artifical external key providers. E.g: think about a product rating/review repository.

  • createReview(ProductBO, UserBO, stars, comment): ReviewBO // with business objects
  • getReviews(ProductBO):Collection<ReviewBO>
  • getReview(ProductBO, UserBO):ReviewBO
  • createReview(ProductRef, UserRef, stars, comment): ReviewBO // with references to business objects

3.1.2 Business Object Functions

The functionalities of business objects are very different. It is not possible to provide a general set of methods. At least, each business object (root entity - accessable via repository) should have an identifier to resolve that business object. An external identifier is necessary also, so the user or external system can identify the object.

  • getID():InternalID
  • getName():ExternalID (name is used as external identifier for business user)

A business object, which acts as aggregate, must not act as a repository for included business object entities. E.g.:

  • BasketBO.addProduct(ProductBO): LineItemBO
  • BasketBO.removeProduct(ProductBO) or/and
  • BasketBO.removeLineItem(LineItemBO)
  • BasketBO.getLineItems():List<LineItemBO>

The BasketBO does not need to provide "find" or "getByID" methods.

Note

Intershop highly recommends that each method call ends in a valid state of the business object. So it is not necessary to call other methods to correct the state of the business object.

3.1.3 Business Object Constraints

As explained business objects matches real world objects. So the business object API must avoid a close binding to the implementation or used technology. E.g., avoid references to:

  • Persistence classes
  • Rendering technology (like ISML template names, CSS classes - except it is the special intention like CMS)
  • Internal implementation (e.g., Exceptions - a user interaction should never throw an exception in case of wrong behaviour of the user)

4 App-specific Business Objects

The behavior of the business object layer can be changed dynamically with respect to the application (or channel) that uses the business object.

App-specific Business Objects

5 Business Object Layer Terms

5.1 Aggregates

An aggregate represents a certain business functionality that "belongs together". It can be seen as the equivalent for the term "engine" that is used for technical features.
In the business object layer, an aggregate consists of entities, value objects, repositories, extensions and context objects. It is managed in its own cartridge. Aggregates must be built to be self-contained in order to be usable in different kinds of applications.

Aggregates

5.2 Entities

An entity is a business object that has its own identity. It can reference other business objects within the aggregate or in other aggregates, and it can hold attributes (e.g., it has a state). Entities can have methods for executing business operations, which usually will manipulate the object graph. Usually, entities are mapped to some underlying persistence layer, so they only represent a wrapper around some delegate object.

An aggregate comes with a root entity object, which serves as the entry point and which manages the inner entities. The inner objects (e.g., entities) cannot be created directly from the outside, but they can only be accessed via the methods of the root object (or indirectly via other entities, starting from root).

The root entity can be retrieved from a repository by its ID. Only the root entity type is accessible from a repository, but no inner entities. The ID of an entity has a local scope within the aggregate / repository.

Entities

 

Example:
// BasketBO.java
package com.intershop.component.basket.capi;
import com.intershop.beehive.businessobject.capi.BusinessObject;
public interface BasketBO extends BusinessObject
{
  public BasketLineItemBO addProductToBasket(ProductBO product);
  public Collection<? extends BasketLineItemBO> getLineItems();
  public BigDecimal getTotal();
  public BasketBORepository getRepository();
}

5.3 Value Objects

Value objects are defined by the values of their attributes. Their identity is unimportant. They can be set as the values of attributes at entities, or they can represent intermediate results, for example, during calculations.

Value Objects

5.4 Repositories

A repository is responsible for the life cycle management of business objects. It handles the creation of new objects, their persistence in the underlying persistence layer, their look-up by some object IDs or other search criteria, and their deletion.
There may be multiple different repository implementations for the same type of business object. For example, the BasketBORepository interface could be implemented by an implementation that holds baskets in memory only. Another implementation could store them in the database via the ORM engine.
For each aggregate, there is only one repository which manages the root entity. All other entities can be accessed via the root entity methods only.

Repositories

Example
// BasketBORepository.java
package com.intershop.component.basket.capi;
import com.intershop.beehive.businessobject.capi.BusinessObjectRepository;
public interface BasketBORepository extends BusinessObjectRepository
{
  public BasketBO createBasket();
  public BasketBO getBasketByID(String id);
  public Collection<? extends BasketBO> getAllBaskets();
  public void deleteBasket(String id);
}

5.5 Persistence Layer Mapping

The business object layer is designed for optimum usability by application programmers. It comes with a nicely designed API which reflects all important domain concepts as Java interfaces, methods and so on. The business object layer API serves as the primary API for programmers in customer projects or Intershop solutions and hides the internal implementation details (like the mapping to the persistence layer) from them. It has to remain stable for a long time.

In contrast, the underlying persistence layer for a business object is designed for maximum performance (read, write). The beauty and stability of the API is secondary, as it will not be exposed to any application programmers. The persistence layer implementation is subject to optimization and therefore to permanent change. It may be redesigned for a better performance without breaking the business object API.

The underlying persistence layer will therefore have a different object structure than the business object layer. Which structure to choose cannot be defined here, as it depends on the actual requirements for the business object.

Mapping

For now, we will only provide an implementation of the business objects API that directly operates on their underlying persistence representation. If there are multiple different implementations of the business object interfaces (e.g., multiple repository implementations), this means that all business methods in an object will have to be re-implemented again in each different implementation. This can be avoided by defining an additional "storage" API, which abstracts the persistence layer from the business API. However, due to the increased implementation efforts we will not provide such an API for now. If necessary, it can be introduced later without breaking the business object API.

5.6 Extensions

In order to support the customization and extension of functionality of business objects, business objects can be enhanced by attaching an extension object to them. Extensions are instantiated by extension factories. An extension factory can decide whether it is applicable for a given business object and can create an extension instance for this object. The decision may include checks of the interface type, the implementation type or even the attributes (state) of the business object.
The list of available extension factories is the key to achieve an app-specific behavior: by attaching different lists to different apps, a business object can behave differently, depending on the app from which it is accessed.

Business Object Extensions

Example:
//RepositoryBOBasketExtensionImpl.java
public class RepositoryBOBasketExtensionFactory extends AbstractDomainRepositoryBOExtensionFactory
{
  /**
  * The ID of the created extensions which can be used to get them from the business object later.
  */
  public static final String EXTENSION_ID = "BasketBORepository";
  @Override
  public BusinessObjectExtension<RepositoryBO>
  createExtension(RepositoryBO repository)
  {
    return new RepositoryBOBasketExtensionImpl(EXTENSION_ID, repository);
  }
}

5.7 Business Object Context

Context objects are used to provide the run-time context for business objects and their methods. Therefore, every business object lives in a business object context. The context object holds the list of available extension factories. Additionally, it also handles the caching of business object instances.

Every entity implementation will get a reference to the context in which it was instantiated.

6 Business Object Layer Implementation

6.1 Business Object Framework

In order to support the development of business objects, a number of super classes and interfaces are provided, which must be subclassed or implemented. The framework classes are located in the cartridge businessobject.

The image shows the conceptual structure of the framework.

Business Object Framework

6.2 Repository Types

A business object repository can be implemented in several ways:

  • The implementation could directly access the underlying persistence layer. This means, the repository provides all objects of a certain type without any additional partitioning. Such a implementation is needed as an entry point into the business object graph. In order to get such a repository, a repository factory must be implemented that must be registered at the business object engine.
  • The implementation could be represented by a business object, which internally serves as a repository for other business objects. With this, multiple repositories (like domains in Enfinity) that represent different partitions can be built. Example:
    • an address book can be seen as a repository for addresses
  • The implementation can be provided as an extension for another business object. For example, there can be a general RepositoryBO that is internally mapped to a Domain, and a BasketBORepository is implemented by an extension that can be attached to the RepositoryBO and internally manages {{BasketPO}}s (which require a domain).
    • a domain in Enfinity can be seen as a repository for products, baskets, line items, ... (almost everything in the database)

Repository Types

6.3 Business Object Custom Attributes

It may be necessary for an extension to store custom data in the host object. For this purpose, the framework defines an API that can be used to set or get custom attributes and localized custom attributes at a business object. An extension can be implemented that provides access to such custom attributes. How this API is mapped to an underlying implementation is up to the implementer of a business object.
In Enfinity, there is an implementation of such an extension that is applicable to all business objects that internally delegate to an ExtensibleObject. The business object attributes API is mapped to the extensible object attribute values.

6.4 Business Object Reference Implementation

The whole concept has been evaluated using a reference implementation. This implementation is completely autonomous and mimics several core concepts of Enfinity. It serves as a showcase how several issues can be solved and how certain things must be implemented when developing or working with business objects.

6.5 Business Object Life Cycle

We must distinguish between two different life cycles:

  • the life cycle of the Java object representing a business object
  • and the life cycle of the persistent entity.

The Java instance is bound to the context, so it is valid as long as the context is in use. In Enfinity, such contexts will have a lifetime of one request, so after the request the BO instance will become invalid and cannot be used anymore.

The persistent entity has a different life cycle, since such objects can exist for a long time (e.g., as entries in the database).

6.6 Business Object Change Listener

Business objects can notify interested listeners about important life cycle events, like object creation, object changes and object deletion. Such events refer to the persistent life cycle of the object, not the Java life cycle.

Using this notification mechanism, extensions can register themselves at their host object in order to perform necessary cleanup or initialization actions of their underlying persistent state.

6.7 Transaction Handling

The business object layer does not define its own transaction handling. Instead, it relies on the transaction handling of the underlying persistence layer (if applicable).
In Enfinity, transactions are controlled by the pipeline. This will not change with a new business object layer. The business object implementation should not handle its own transactions, as it may be necessary to synchronize the commits of multiple different business objects with each other.
Ideally, the business object developer does not have to care about transactions at all. They will be handled by the pipeline engine and the underlying ORM engine.

6.8 Identifiers

Business objects (e.g., root entities) must have an ID that can be used to look them up later. It can be retrieved by the method getID(), and it is generated by the repository that creates the business object. In the business object layer, such IDs are simply defined to be a string attribute. Its purpose is an internal one - it is used to look up objects within a repository, where the repository can also be an entity within an aggregate object (see below). The possible values are not specified here, this is up to the repository implementation. For repositories that map the objects to ORM objects, we recommend to use the UUID of the mapped PO as ID.
Depending entities, e.g., objects that are reachable via the root entity only, may also have an ID. This ID can be used within the scope of the aggregate to identify the object. For example, such entities could be looked up by using some method in the root entity (or the entity being the repository of the BO in question). How the IDs of internal entities map to the underlying persistence layer is also left open here. Some implementations may prefer to use UUIDs, some other implementations may rely on auto-generated sequence numbers in the datastore, some others may need to use compound keys, and so on. Preferably, the ID of non-root BOs contains the ID of their parent BO. This allows looking them up from the outside repository.
The ID is for internal purposes only and should not leave Enfinity (the only exception is the user's browser, so the ID may take part in web forms).

Typically, business objects are also referred by a so-called "semantic key". This is the ID made available to the business side. It is used when communication outside of Enfinity takes place, e.g., for exports and imports. There is no defined structure or naming for this kind of ID; it is up to each BO to define it. It must be documented in the documentation of the BO API. As far as it exists, it needs to contain the name of the repository where the BO is located, and it must be globally unique. The business ID can be provided by the repository, but it can also be set from the outside (e.g., when importing data). For aggregate objects, the "inner" objects also need a semantic key, that must contain the semantic key of their parent object. If no such key can be derived from the existing business data, it needs to be created artificially.

6.9 Caching

Business objects are valid as long as their context is valid. Since the context is usually bound to a single request, the lifetime of an object is the time until the request is processed and finished. On the next request, a new context object will be created, so the business object must be retrieved from the repository again.
The repository may implement a caching strategy that returns the same business object instance again in case an object for a given ID is looked up multiple times with the same context.

Additionally, the underlying persistence layer (like the ORM engine) is free to implement its own caching strategy to allow caching across multiple requests or even threads.

Some methods in a business object may be very expensive, so their result should be cached. For example, operations like calculating the total of a basket or converting some XML CLOB from the persistent object back into a Java representation are very expensive. The result of such an operation can be set as an instance variable at the business object.
Since the business object depends on the context, and the context is different for each concurrent request, the transactional isolation between objects is guaranteed.

6.10 Cache Invalidation

If a business object holds its own internal cached state (for performance reasons), it may be necessary to notify it regarding changes that affect the validity of the cached state. For example, when a basket caches the total of all its line items, this total becomes invalid if the quantity of a line item is changed.
There are two possible ways to implement such a cache invalidation:

  • the business object can register at each important business object directly using the Business Object Listener mechanism and clear the cached state if such an object is changed
  • there can be a central cache invalidation handler that is passed around the entire aggregate (e.g., all business objects within the aggregate) and that notifies all registered objects

The invalidation might have two scopes:

  • a business object changes some data, which invalidates subsequent data of this object
  • the changes to the business object may invalidate data of other objects related to the changing object (see the example above)

However, since each of the implementation of the objects in an object graph may be exchanged independently, the first case cannot really happen - there is always the chance that someone out there is relying on the changed data. This means that, when the exposed data is changed, all interested parties must be invalidated (when data that is never exposed changes, there are no subsequent objects that need to be notified).

For doing this, a central BusinessObjectInvalidationHandler is provided. This handler knows about all objects that need invalidation, and propagates the invalidation event to them. So the process consists of 2 steps

  • first, the root aggregate creates a new handler object on its own creation. This handler is then given to all child objects depending on this aggregate, which in turn can register themselves with the handler
  • when an object changes exposed data, it tells the handler, which in turn notifies all subscribed objects (this includes the calling object, if it is registered)

Members of an aggregate can pass the InvalidationHandler as a constructor argument. The handler is needed for

  • giving the instances to other, newly created instances
  • register themselves as listener (see below)
  • start an invalidation process

Note that the invalidate() call to the handler is sufficient - the caller will be called back from the handler when it is registered. There is no need for the caller to invalidate its data directly before or after the call to the handler (though it does not harm).

When registering as a listener, an anonymous inner class should be used. This avoids exposing the Listener interface on the business object itself, so these methods cannot be called from the outside. This ensures that they are not called by accident.

7 Examples

7.1 General Examples

Example - API Example Implementation Example Extension

7.2 Image BO Real-world Examples

BO_Layer_v2Cartridges BO_Layer_v2_SampleImpl BO_Layer_v2

7.3 References

 

 

 

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Support Tickets