Resource Repositories
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.
- E.g. a) Create a new shipping address for a customer's profile. b) Prompting a user to double-check their actions if they've added two of the same item to the cart. See Using submit() for more information.
- findOne(): Finds (reads) a single entity.
- E.g. Find a specific shipping address for a customer's profile.
- findAll(): Finds (reads) all entities of a specified type.
- E.g. Find all shipping addresses for a customer's profile.
- delete(): Deletes an entity.
- E.g. Delete a shipping address from a customer's profile.
- update(): Updates an entity.
- E.g. Delete a shipping address from a customer's profile.
Using submit()
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.
- E.g. Create a new shipping address for a customer's profile.
- Provide users with messaging.
- E.g. 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.
- E.g. Incrementing the amount of an item that currently exists in the cart when the user adds that item to the cart a second time.
Links Repositories
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:
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.
- E.g. Get the paginated results for a searched keyword.
- getElements(): Returns linked paginated items for the resource.
- E.g. Get the item identifier from a keyword search result.
- getPagingLinks(): Returns links for navigating between results
pages.
- E.g. 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.
- E.g. Get all the addresses for a customer as choices for a billing address.
- getChoice(): Returns a particular choice.
- E.g. Get a particular address for a customer.
- selectChoice(): Select a particular choice.
- E.g. 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 out of the box methods or implement new ones.
Implementing Additional CRUD Functionality
A repository may not implement all CRUD 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.
Adding Fields to a 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.
An example of this is examined in detail in the Extend a Helix Resource tutorial.
Creating New Repositories for New Resources
- 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 or LinksRepository interface.
- Implement at least one CRUD method provided by the interfaces.
- Use either public or protected 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
- 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 Commerce Engine Services
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 a Repository into a Prototype
In order to inject a Repository (implementing any one of the Repository, LinksRepository, AliasRepository, PaginationRepository, SelectorRepository interfaces) 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) { ... }
Make sure to type the generic type parameter of the Repository classes to the appropriate ResourceEntity and ResourceIdentifier.