Resource Repositories
A repository class provides information through either Commerce Engine or another repository to a resource via a consistent set of CRUD (Create, Read, Update, Delete) methods. Repositories translate between commerce domain objects and API representations, abstracting the complexity of Commerce Engine’s databases and business logic from the API layer.
How Repositories Work
Repositories typically implement either one of the interfaces in the com.elasticpath.repository
package, and one or more of their methods. A resource may have multiple repositories implemented.
CRUD Repositories
CRUD repositories implement the Repository
interface, and at least one of the following methods:
submit()
Creates a new entity or updates an existing entity. If the entity does not exist, it is created. If it does, it is either updated based on the information submitted, or its identifier is returned. For example:
Create a new shipping address for a customer’s profile
Prompting a user to double-check their actions if they’ve added two of the same item to the cart
The submit()
method is available for use in scenarios where you need to do any of the following upon a form submission:
Consistently create a new entity
Example: Create a new shipping address for a customer’s profile.
Provide users with messaging
Example: Prompting a user to double-check their actions if they’ve added two of the same item to the cart.
Update the status of an existing entity
Example: Incrementing the amount of an item that currently exists in the cart when the user adds that item to the cart a second time
findOne()
Finds (reads) a single entity.
Example: Find a specific shipping address for a customer’s profile.
findAll()
Finds (reads) all entities of a specified type.
Example: Find all shipping addresses for a customer’s profile.
delete()
Deletes an entity.
Example: Delete a shipping address from a customer’s profile.
update()
Updates an entity.
Example: Delete a shipping address from a customer’s profile.
Links Repositories
Links repositories are used to link an identifier of one resource type with zero or more identifiers of another resource type. They implement the LinksRepository
interface and at least one of the following methods:
getElements()
Returns all identifiers of the linked resource type.
Example: Return all identifiers for items on a wishlist.
deleteAll()
Deletes all elements of the linked resource type.
Example: Delete all items from a wishlist (but not the wishlist itself).
Alias Repositories
Alias repositories resolve a resource identifier from an alias (pointer). Aliases are typically human-readable, for example the default
cart alias.
Aliases can also differentiate between functionally identical but conceptually different resources: for example, the idea of a holiday cart, which may require differentiation from a standard cart for business reasons. Alias repositories implement the the AliasRepository
interface and the following method:
resolve()
Returns the linked identifier for the alias identifier.
Example: Resolve a default cart identifier to a shopping cart identifier.
Pagination Repositories
Pagination repositories supply additional pagination information for entities and decipher links among and between paginated results. They implement the PaginationRepository
interface and all of the following methods:
getPaginationInfo()
Returns the pagination entity containing pagination information for the resource.
Example: Get the paginated results for a searched keyword.
getElements()
Returns linked paginated items for the resource.
Example: Get the item identifier from a keyword search result.
getPagingLinks()
Returns links for navigating between results pages.
Example: Get paging links between keyword search results pages.
Selector Repositories
Selector repositories help in implementing the selector resource pattern. For example, a resource for selecting a billing address or a resource for selecting shipping address are implemented using the selector resource pattern. Selector repositories implement the SelectorRepository
interface and all of the following methods:
getChoices()
Returns the choices for the selector.
Example: Get all the addresses for a customer as choices for a billing address.
getChoice()
Returns a particular choice.
Example: Get a particular address for a customer.
selectChoice()
Select a particular choice.
Example: Select an address as a billing address.
Extending Repositories
To add functionality or fields to an entity, or to change its default behaviour, you must extend a repository. To extend a repository, either override its existing methods or implement new methods.
Extend a repository in the ep-commerce/extensions/cortex/repositories/src/main/java
module in the following format:
@Component(property = {Constants.SERVICE_RANKING + ":Integer=101"}, service = Repository.class)
public class repository<E extends MyResourceEntity, I extends MyResourceIdentifier>
extends repositry<MyResourceEntity, MyResourceIdentifier> {
Where MyResourceEntity
is any default Elastic Path Commerce entity and MyResourceIdentifier
is any default Elastic Path Commerce identifier.
note
For more information about the SERVICE_RANKING
property, see the OSGi javadoc. If more than one service implements the specified class, the one with the highest ranking is returned. The default ranking is 0
.
Any class with service ranking more than the default value overrides the default class.
Implementing Additional CRUD Functionality
A repository may not implement all CRUD (Create Read Update Delete) methods by default. To implement additional CRUD functionality, implement the desired CRUD method in an existing repository.
For example, the ability for a user to create more than one wishlist per profile is not implemented by default. To implement this functionality, extend the out of the box repository by adding a create()
method to it.
Changing Default Behaviour of CRUD method
To change the default behaviour or business logic of an repository, override a repository’s CRUD method with the desired functionality.
Adding Fields to Repository
Sometimes, when extending a Commerce Engine domain object like addresses, you may need to pass extra fields to the repository and resource. To add fields to an entity that the repository returns, override the appropriate CRUD methods in a repository class to include the extra fields.
For a tutorial on extending Helix resources, see the Tutorials repository. In the ep-commerce-examples
directory for this version, select the examples
branch and search for extend Helix
.
Creating New Repository for New Resources
When creating a custom resource, you will have to write the repositories from scratch. Your repository class should do the following:
- Annotate the repository class with
org.osgi.service.component.annotations.Component
, to ensure it is exposed as an OSGi service and injectable - Implement the
Repository
orLinksRepository
interface - Implement at least one CRUD method provided by the interfaces.
Use either
public
orprotected
to ensure method extensibility
Implementing CRUD methods
Repositories implement an interface’s methods based on a resource’s intended functionality. To implement a CRUD method, you will typically invoke one or more Commerce Engine services or legacy repository methods using the ReactiveAdapter
class.
ReactiveAdapter
helps to integrate CE Services and legacy repositories into reactive Rx execution chains.
To use the ReactiveAdapter
methods, inject an instance of ReactiveAdapter
into your repository implementation, then use one of its methods to call a Commerce Engine service or legacy repository:
@Inject
CarsRepositoryImpl(
@Named("reactiveAdapter")
final ReactiveAdapter reactiveAdapter) {
this.reactiveAdapter = reactiveAdapter;
}
...
Calling Commerce Engine Services
To initiate a non-blocking Commerce Engine service call with automatic null and exception handling, use one of the following ReactiveAdapter
methods:
fromService()
Returns a RxJava Observable which wraps the return value of a Commerce Engine service.
fromServiceAsSingle()
Returns a RxJava Single which wraps the return value of a Commerce Engine Service.
fromServiceAsCompletable()
Returns whether or not an operation was successful.
An example of returning an Observable from a Commerce Engine service is below:
Observable o = reactiveAdapter.fromService(() -> ceService.myMethod())
ReactiveAdapter
methods handle InvalidBusinessStateException
(HTTP 409) and EpValidationException
(HTTP 400) exceptions automatically, and return appropriate structured error messages.
Custom Exception Handling when Calling
If needed, you can still do custom exception handling:
Completable c = reactiveAdapter.fromServiceAsCompletable(() -> {
try {
return customerService.update(customer);
} catch (UserIdExistException error) {
throw exceptionTransformer.getResourceOperationFailure(error);
}
});
}
Or
Completable c = reactiveAdapter.fromServiceAsCompletable(() -> customerService.update(customer))
.onErrorResumeNext(throwable -> {
try {
throw throwable;
} catch (NullPointerException npe) {
return Completable.error(ResourceOperationFailure.notFound());
}
});
}
Calling Legacy Repositories
You may have to call a legacy resource’s repository from your resource. Legacy repository methods return an ExecutionResult
.
ReactiveAdapter
provides methods for returning this data to your repository as a RxJava
type, depending on the type of data wrapped in the ExecutionResult
.
If the legacy repository call returns an ExcecutionResult
containing an Iterable as data use ReactiveAdapter.<String, List<String>>fromRepository()
:
Observable o = reactiveAdapter.<String, List<String>>fromRepository(() -> repository.getShoppingItems())
If the legacy repository call returns an ExecutionResult
containing a non-Iterable, non-null Object
use ReactiveAdapter.<String, List<String>>fromRepositoryAsSingle()
:
Single s = reactiveAdapter.<String, List<String>>fromRepositoryAsSingle(() -> repository.getDefaultCard())
If the legacy repository call returns an ExecutionResult
containing a null value as data use ReactiveAdapter.<String, List<String>>fromRepositoryAsCompletable()
:
Completable c = reactiveAdapter.<String, List<String>>fromRepositoryAsCompletable(() -> repository.updateCustomer())
All these methods are non-blocking. Typically the legacy repository method does the exception handling.
Injecting Repository into a Prototype
In order to inject a Repository into a prototype class, use the @Inject
annotation in combination with the @ResourceRepository
annotation in the method signature:
@Inject
public MyPrototype(@ResourceRepository Repository<MyEntity, MyIdentifier> repository)
{ ... }
A Repository is a class that implements any of the following interfaces:
Repository
LinksRepository
AliasRepository
PaginationRepository
SelectorRepository
Make sure to type the generic type parameter of the Repository classes to the appropriate ResourceEntity
and ResourceIdentifier
.