Broadleaf Microservices

Extending Out-of-Box Repositories

Repositories in Broadleaf

Broadleaf provides numerous repositories out-of-box utilizing Spring Data and our custom narrowing behaviors, see Repository Design In Detail.

Extending/overriding Broadleaf repositories is not supported. We recommend defining your own repository implementation and reference it wherever it’s needed.

Spring Data and Customized Repositories

In Broadleaf, you can see that most of the time we have two repository interfaces for each domain, one for Spring Data and one for custom implementations.

The repository for Spring Data is simply an abstraction to reduce the amount of boilerplate code using the Spring Data framework. This interface allows us to define query methods by simply defining method names.

Example: AccountAddress repository
@NoRepositoryBean
public interface AccountAddressRepository<D extends Trackable>
        extends TrackableRepository<D>, TrackableRsqlFilterExecutor<D>,
        CustomizedAccountAddressRepository<D> {

    @NonNull
    @Policy(operationTypes = OperationType.READ)
    Page<D> findByAccountContextId(@NonNull String accountId,
            Node filters,
            Pageable page,
            ContextInfo contextInfo);

    @Policy(operationTypes = OperationType.READ)
    Optional<D> findByContextIdAndAccountContextId(@NonNull String contextId,
            @NonNull String accountId,
            ContextInfo contextInfo);
}
Note
The Spring Data repository extends the customized repository. Doing so combines the Spring Data repository methods and custom implementations and makes it available in one repository bean.
Note
If unfamiliar with the Spring Data repository patterns, you can learn more by referencing Spring Data’s official documentation on repositories.

However, sometimes we need custom implementations for some repository methods, this is when customized repositories come into play.

Example: CustomizedAccountAddress repository
public interface CustomizedAccountAddressRepository<D extends Trackable> {

    @Policy(operationTypes = OperationType.UPDATE)
    void clearDefaultForAddressType(AccountRef account,
            String accountAddressType,
            @Nullable ContextInfo context);

    @Policy(operationTypes = OperationType.READ)
    void someCustomMethod(AccountRef account,
            @Nullable ContextInfo context);
}
Note
For more details about Spring Data repository customization, you can learn more by referencing Spring Data’s official documentation on custom implementations.

Adding a New Repository for an Exising Domain

In this tutorial, we will define our own repository for the AccountAddress domain and add some custom behavior.

Defining a new repository for an existing broadleaf domain is very much similar to defining a repository for a new domain. See Tutorial on How to Create Repository for a New Domain.

Adding Customized and Spring Data Repositories

First, we need to define the repository interfaces. We’ll define a customized one and one for Spring Data. See What Spring Data and Customized Repositories Are.

Customized repository for AccountAddress
public interface MyCustomizedAccountAddressRepository<D extends Trackable> {
    void myNewMethod();
}
Spring Data repository for AccountAddress
@NoRepositoryBean
public interface MyAccountAddressRepository<D extends Trackable> extends TrackableRepository<D>,
        TrackableRsqlFilterExecutor<D>, MyCustomizedAccountAddressRepository<D> {
}
Note
The Spring Data repository extends the customized repository. Doing so combines the Spring Data repository methods and custom implementations and makes it available in one repository bean.

Then, we need to define the provider-specific repository bean.

JPA provider-specific repository
@Repository
@Narrow(JpaNarrowExecutor.class)
public interface JpaMyAccountAddressRepository<D extends JpaAccountAddress>
        extends MyAccountAddressRepository<D> {
}
JPA provider-specific customized repository
public class JpaMyCustomizedAccountAddressRepository<D extends JpaAccountAddress>
        implements MyCustomizedAccountAddressRepository<D> {

    @Override
    public void myNewMethod() {
        // some custom logic here
    }
}

Repository Configuration

Now, we will define a configuration class that will scan our repositories.

Since AccountAddress is in Broadleaf’s Customer microservice, we will add the configuration class to the same microservice, which will also allow us to use the same entity manager factory and transaction manager that the Customer microservice already uses.

JPA provider-specific configuration
@Configuration
@AutoConfigureBefore(CustomerJpaAutoConfiguration.class)
@EnableJpaRepositories(
        basePackages = "com.mycompany.provider.jpa.tutorial.repository",
        repositoryFactoryBeanClass = JpaTrackableRepositoryFactoryBean.class,
        entityManagerFactoryRef = CUSTOMER_ENTITY_MANAGER_FACTORY,
        transactionManagerRef = CUSTOMER_TRANSACTION_MANAGER)
public class MyJpaAutoConfiguration {
    @Bean
    MyCustomizedAccountAddressRepository<JpaAccountAddress> jpaMyAccountAddressRepositoryImpl() {
        return new JpaMyCustomizedAccountAddressRepository<>();
    }
}
Note
Remember to create a MyCustomizedAccountAddressRepository bean with the name of the Spring Data repository bean, jpaMyAccountAddressRepositoryImpl (the Impl postfix is important). Doing so ties the custom implementation to the Spring Data implementation.
Note
Don’t forget to register the JPA configuration class in spring.factories under org.springframework.boot.autoconfigure.EnableAutoConfiguration (and org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa if using that test configuration).

Using the new Repository

Now that the repository is set up and configured, we can inject the new repository bean wherever is needed.

Injecting the new repository bean
private final MyAccountAddressRepository<Trackable> myAccountAddressRepository;
Calling our custom method
@GetMapping("/new-endpoint")
public String myNewEndpoint(@ContextOperation(OperationType.READ) ContextInfo contextInfo) {
    myAccountAddressRepository.myNewMethod();
}