Domain Service Java SDK

Introduction

The service project SDK provides numerous components to help with the implementation of a Domain Service Project based on the project's design model. That means, whatever you modelled in the design phase will be reflected by pre-generated code.

Structure

SDK consists of some main packages under src/main/generated source folder:

  • api: which holds all API Namespace's schema models, controllers and delegate classes.

  • domain: which holds classes for all entities, services, commands, events and agents that have been modelled in Domain Namespaces.

  • integration: which contains classes and providers that allows for easy integration with dependent API(s) that have been added in Integration Namespaces.

Solution engineers can use these generated classes to implement logic and interact with different modelled components.

Below are the main SDK components that can be used by solution engineers to help to implement project generated implementation files logic.

Attention: Some components are grouped under the namespace that they are modelled.

For example:

// Import Owner entity that was modelled in a namespace with the prefix: cc
import de.knowis.cards.sdk.domain.cc.entity.Owner;

Entity builders

SDK provides Builders for Entities, these builders can be easily used to construct entities using Builder Pattern which allow to write readable, understandable code to set up complex objects.

Namespace entity builders

  • Each namespace (Domain / Integration) has its own entity builder grouping usually prefixed with the namespace acronym.

  • Namespace with prefix cc would get a CcEntityBuilder class that can be injected and groups all the entity builders in that domain.

For example:


// Import Entity Builder of namespace prefixed by cc
import de.cards.sdk.domain.cc.facade;

// Declaring Entity Builder for namespace cc
@Autowired
protected CcEntityBuilder ccEntityBuilder;

// Using Entity Builder
CreditCard creditCardEntity = ccEntityBuilder.getCreditCard().setName("John Doe").build();

Overall entity builders

Now that we learned about Namespace entity builder, SDK also provides a grouping for these namespace entity builders, for ease of access.

There are two types of Entity Builders grouping:

  1. Domain Entity Builder, this groups all domain namespace entity builders.

  2. Integration Entity Builder, this groups all integration namespace entity builders.


// Import DomainEntityBuilder & IntegrationEntityBuilder
import de.cards.sdk.domain.facade.DomainEntityBuilder;
import de.cards.sdk.integration.facade.IntegrationEntityBuilder;

// Declare & Inject Domain Entity Builder
@Autowired
private DomainEntityBuilder domainEntityBuilder;

// Declare & Inject Integration Entity Builder
@Autowired
private IntegrationEntityBuilder integrationEntityBuilder;
      
// Create a customer entity that exists in a domain namespace prefixed by cc.
Customer customer = entityBuilder.cc.customer()
        .setCustomerId("Customer Id")
        .setCustomerName("Joh Doe")
        .setAmixCard(new AmixCard("some id", CardType.debit))
        .build();
Tip: EntityBuilder is also accessible in project generated implementation files (services, commands, agents) derived from their base classes.
// Use the Domain Entity Builder to create an entity that was modelled in domain namespace 
// with prefix: cc
Owner owner = this.domainEntityBuilder.getCc().getOwner().build();

// Use Integrationn Entity Builder to create an entity that was modelled in 
// integration namespace with prefix: cards
AmexCard amexCard = this.integrationEntityBuilder.getCards().getAmexCard().build();

Event builder

SDK provides Builders for Events also, similar to Entity builders.

These builders can be easily used to construct events.

For Example:

// Importing overall Event Builder
import de.knowis.cards.sdk.domain.facade.DomainEventBuilder;

// Declare and Inject Domain Event Builder
@Autowired
private DomainEventBuilder eventBuilder;

// Use the DomainEventBuilder to create an event that was modelled in a domain namespace with prefix cc
CardCreatedEvent event = this.eventBuilder.getCc().getCardCreatedEvent().build();
event.setPayload(payloadEntity);
Tip: EventBuilder is also accessible in project generated implementation files (services, commands, agents) derived from their base classes.

Service facades

Namespace service facades

SDK provides service facades that group all the services inside a namespace to provide easy access, dependency injection and execution of different services.

In the below example both (CcService & OpsService) are namespace facades that group all the services in their respective namespaces where cc & ops are the namespace acronyms.

// Importing cc namespace service facade that provides access to all of its services.
import de.knowis.opeations.cards.sdk.domain.cc.facade.CcService;

// Importing ops namespace service facade that provides access to all of its services.
import de.knowis.opeations.cards.sdk.domain.ops.facade.OpsService;

// Declare and Inject Service Facades
@Autowired
private CcService ccService;

@Autowired
private OpsService opsService;

// Use namespace service facade  to call GetCreditCardDetails & GetCustomerDetails services within that namespace

// Calling GetCreditCardDetails and passing cardIdentificationEntity as service input entity
CreditCard creditCardEntity = ccService.getCreditCardDetails().execute(cardIdentificationEntity);

// Calling GetCustomerDetails and passing cardIdentificationEntity as service input entity
Customer customerEntity = ccService.getCustomerDetails().execute(customerIdentificationEntity);

Overall service facade

Now that we learned about Namespace service facades, SDK also provides a grouping for all these namespace service facades, for ease of access, dependency injection and service execution.

There are two types of these namespace service facade grouping:

  1. Domain Service, this groups all domain namespace services.

  2. Integration Service, this groups all integration namespace services.

Example importing DomainService & IntegrationService


// Import DomainService & IntegrationService
import de.cards.sdk.domain.facade.DomainService;
import de.cards.sdk.integration.facade.IntegrationService;

// Declare & Inject Domain Service Facade
@Autowired
private DomainService domainService;

// Declare & Inject Integration Service Facade
@Autowired
private IntegrationService integrationService;
      
// Calling getCustomer service that belongs to namespace prefixed by cc.
Customer customerEntity = domainService.getCc().getCustomer().execute(customerIdentification);

// Calling loadCardDetails service that belongs to an integration namespace prefixed by cards.
CardDetails cardDetailsEntity = integrationService.getCards().loadCardDetails().execute(cardIdentification);

Command facade

Namespace Command facade

  • For Domain Namespaces SDK provides a command facades that group all the commands inside a namespace to provide easy access, dependency injection and execution of different commands.

  • In the below example both (CcCommand & OpsCommand) are domain namespace facades that group all the commands in their respective namespaces where cc & ops are the namespace acronyms.

// Importing cc namespace command facade that provides access to all of its commands.
import de.knowis.opeations.cards.sdk.domain.cc.facade.CcCommand;

// Importing ops namespace service facade that provides access to all of its commands.
import de.knowis.opeations.cards.sdk.domain.ops.facade.OpsCommand;

// Declare and Inject Service Facades
@Autowired
private CcCommand ccCommand;

@Autowired
private OpsCommand opsCommand;

// Use namespace command facade to call UpdateCreditCardDetails & CreateProfile commands within that namespace

// Calling UpdateCreditCardDetails instance command that belongs to a root entity with local identifier "CreditCard"
CreditCard creditCardEntity = ccCommand.getCreditCard().UpdateCreditCardDetails(cardDetails);

// Calling CreateProfile factory command that belongs to a root entity with local identifier "CustomerProfile"
CustomerProfile customerProfileEntity = opsCommand.getCustomerProfile().CreateProfile(customerData);

Overall Command facade

  • Now that we learned about Namespace command facades, SDK also provides a grouping for all these namespace command facades, for ease of access, dependency injection and service execution.

// Import overall Command facade class
import de.cards.sdk.domain.facade.Command;

// Declare & Inject Command Facade
@Autowired
private Command command;
      
// Calling commands inside a namespace with prefix "cc" 
// UpdateCreditCardDetails is a command that belongs to a root entity with local identifier "CreditCard"
CreditCard creditCardEntity = command.getCc().getCreditCard().UpdateCreditCardDetails(cardDetails);

Repository

  • SDK provides repositories so solution engineer are able to interact with their service project data.

  • Root Entities "aggregates" are the presistable unit, for each root entity there will be an associated repository class generated in the SDK.

  • Depending on your chosen persistence extension "MongoDB" or "RDBMS", the base class of that repository will vary.

MongoDB persistence

  • Each Root Entity instance will be saved into a Mongo DB Collection.

  • The Repository Class through its inheritance chain extends SimpleMongoRepository <T, ID>.

  • The SimpleMongoRepository provides access to several functionalities such as findBy, findAll, count, save, delete....etc.

  • You can directly inject root entity repository or use it using repo which is a repository facade that is available in commands, domain services, external entities and agents base classes.

Example of repository usage for different database operations.

// Build a credit card root entity that exists in a domain namespace prefixed by cc.
CreditCard creditCard = this.entityBuilder.getCc().creditCard()
    .setBankName("MyBank")
    .setCardType(CardType.debit)
    .setCustomer(customer)
    .setIssueDate(OffsetDateTime.now())
    .build();

// Save a credit card root entity.
creditCard = this.repo.getCc().getCreditCard().save(creditCard);

// Retrieve all credit card root entities
List<CreditCard> cards = this.repo.getCc().getCreditCard().findAll();

// Retrieve all credit card root entities filtered by CardType
ExampleMatcher matcher = ExampleMatcher.matchingAll().withIgnorePaths("BankName", "Customer", "IssueDate");
Example<CreditCard> example = Example.of(creditCard, matcher);

List<CreditCard> queryResult = repo.getCc().getCreditCard().findAll(example);

// Update credit card root entity
creditCard.setCardType(CardType.credit);
repo.getCc().getCreditCard().save(creditCard);

// Delete credit card root entity
repo.getCc().getCreditCard().delete(creditCard);

//Count all credit card root entities
long count = this.repo.getCc().getCreditCard().count();

Example of using inside a command implementation using declared repository from command base class.

//... imports

@Service
public class CardCommand extends CardCommandBase {

  private static Logger log = LoggerFactory.getLogger(CardCommand.class);
  
  public CardCommand(DomainEntityBuilder entityBuilder, DomainEventBuilder eventBuilder, EventProducerService eventProducer, Repository repo) {
    super(entityBuilder, eventBuilder, eventProducer, repo);
  }


  @Override
  public Card createCreditCard(CreditCard creditCard) throws CreditCardCreationError {

    log.info("CardCommands.createCreditCard()");
        // ..... command logic

        // Using this.repo to find and save an updated Card root entity
        try {

            Optional<Card> cardRootEntity = this.repo.getCc().getCard().findById(creditCard.getId());
        
          // Update card if it exists
          if(cardRootEntity.isPresent()){

            cardRootEntity.setActive(true);

            log.info("Updated Credit Card Root Entity with Id {}", cardRootEntity.getId());
            
            // Save updated card
            this.repo.getCc().getCard().save(cardRootEntity);
            } else {
              throw CardNotFoundException("Could not find card with Id {}", creditCard.getid());
            }

    } catch(Exception e) {
      // ... Exception handling logic
    }

    return cardRootEntity;
  }

Example of injecting and using CardRepository in a mapper utility.

//... imports

// Importing *CardRepository*
import de.knowis.cards.sdk.domain.card.repository.CardRepository;

@Service
public class Mapper {

  private static Logger log = LoggerFactory.getLogger(Mapper.class);

  // Injecting *CardRepository*
  @Autowired
  private CardRepository cardRepo;

  @Override
  public Optional<Card> mapToRootEntity(String cardId){
        
        Optional<Card> card= cardRepo.findById(cardId);

    return card;
  }

For more information about the functions of generated repositories that is inherited from SimpleMongoRepository See Official Spring Documentation

Tip: Repository find / count operations on a Root Entity that has child entities, will include these children as well.

MongoDB inheritance support

  • Within MongoDB Persitence support, multiple inheritance is supported through interfaces and concrete classes.

  • Root Entity will have a corresponding interface that extends all its modelled parents, that interface will be implemented by a single concrete class.

public interface RelationRole extends ManagerialRelation, MarketingRelation {

  String getRoleType();

  void setRoleType(String roleType);

  BigDecimal getShare();

  void setShare(BigDecimal share);

}
  • Finally the concrete class that represents that entity will implement that interface (i.e. will have properties of its own and those inherited from parent entities).

public  class RelationRoleEntity extends EntityBase implements RelationRole {

  public RelationRoleEntity(){}

  @Field(name="roleType")
  @NotEmpty
  private String roleType;
  
  @Field(name="share", targetType = FieldType.DECIMAL128)
  private BigDecimal share;
  
  RelationRoleEntity(RelationRoleBuilder builder) {
    this.roleType = builder.roleType;
    this.share = builder.share;
    
  }

RDBMS persistence

  • The Repository Interface through its inheritance chain extends interface JpaRepository <T, ID>

  • The JpaRepository provide access to several functionalities such as findBy, findAll, count, save, delete....etc.

  • You can directly inject root entity repository or use it using repo which is a repository facade that is available in commands, domain services, external entities and agents base classes.

Example of repository usage for different database operations.

    // Build a credit card root entity that exists in a domain namespace prefixed by cc.
    CreditCardEntity creditCard = this.entityBuilder.getCc().getCreditCard()
        .setBankName("MyBank")
        .setCardType(CardType.debit)
        .setCustomer(customer)
        .setIssueDate(OffsetDateTime.now())
        .build();

    // Save a credit card root entity.
    creditCard = this.repo.getCc().getCreditCard().save(creditCard);

    // Retrieve all credit card root entities
    List<CreditCardEntity> cards = this.repo.cc.creditCard.findAll();

    // Update credit card root entity
    creditCard.setCardType(CardType.credit);
    this.repo.getCc().getCreditCard().save(creditCard);

    // Delete credit card root entity
    this.repo.getCc().getCreditCard().delete(creditCard);

    //Count all credit card root entities
    long count = this.repo.getCc().getCreditCard().count();

Example of using inside a command implementation using declared repository from command base class.

//... imports

@Service
public class CardCommand extends CardCommandBase {

  private static Logger log = LoggerFactory.getLogger(CardCommand.class);
  
  public CardCommand(DomainEntityBuilder entityBuilder, DomainEventBuilder eventBuilder, EventProducerService eventProducer, Repository repo) {
    super(entityBuilder, eventBuilder, eventProducer, repo);
  }

  @Override
  public Card createCreditCard(CreditCard creditCard) throws CreditCardCreationError {

    log.info("CardCommands.createCreditCard()");
        // ..... command logic

        // Using this.repo to find and save an updated Card root entity
        try {

            Optional<CardEntity> cardRootEntity = 
            this.repo.getCc().getCard().findById(creditCard.getId());
        
          // Update card if it exists
          if(cardRootEntity.isPresent()){

            cardRootEntity.setActive(true);

            log.info("Updated Credit Card Root Entity with Id {}", cardRootEntity.getId());
            
            // Save updated card
            this.repo.getCc().getCard().save(cardRootEntity);
            } else {
              throw CardNotFoundException("Could not find card with Id {}", creditCard.getid());
            }

    } catch(Exception e) {
      // ... Exception handling logic
    }

    return cardRootEntity;
  }

Example of injecting and using CardRepository in a mapper utility.

//... imports

// Importing *CardRepository*
import de.knowis.cards.sdk.domain.card.repository.CardRepository;

@Service
public class Mapper {

  private static Logger log = LoggerFactory.getLogger(Mapper.class);

    // Injecting *CardRepository*
    @Autowired
    private CardRepository cardRepo;

  @Override
  public Optional<CardEntity> mapToRootEntity(String cardId){
        
        Optional<CardEntity> card= cardRepo.findById(cardId);

    return card;
  }

For more information about the functions of generated repositories that is inherited from JpaRepository See Official Spring Documentation

Tip: Repository find / count operations on a Root Entity that has child entities, will include these children as well.

RDBMS inheritance support

  • Inheritance is one of the key concepts in Java, and it's used in most domain models.

  • SQL doesn't support this kind of relationship, SDK used the JPA single table inheritance strategy to map inheritance between Entities.

  • Within RDBMS multiple inheritance is not supported, Root Entity can only have one parent.

  • Each Root Entity will have an associated table name.

  • Root Entities that have parent(s), their upmost parent table name will be used as this is the JPA implementation for single table inheritance strategy.

Single table inheritance strategy
  • Single Table inheritance: the simplest and typically the best performance when doing queries.

  • Single Database Table: will be used to store every attribute of every class in the hierarchy.

  • Discriminator Column: used to determine which class the row belongs to, each class in the hierarchy defines its own unique discriminator value.

Entity inheritance model example

entity inheritance model example
Single table inheritance example

single table inheritance example

Mapping in RDBMS

Unlike MongoDB persistence support see MongoDB persitence support, where there is only db references used See Official mongo DBRef documentation

RDBMS have relationships between tables, below is how the relationships between entities is reflected in persistence.

Root Entity

Mapped to its own tables with support for One-To-Many and One-To-One Foreign Key relationships with other root entities.

(Value) Entity

Common use case as value objects, input to commands, services, etc.

If modelled as a property of a root entity, its data will be embedded in the root entity's table. In case of a collection (list of value entities), a collection table will be created and linked with root entity.

External Entity

Common use case as reference to an entity that resides outside the domain (or even within an external system). The same embedded strategy that applies to value entities will be applied here.

Mapping example

Below is a representation of an entity hierarchy and how its mapped into a DB2 RDBMS.


Entity examples

Mapping example

API dependency provider

For each API dependency added in Integration Namespaces, there will be a provider that allows for making REST calls to that dependent API endpoints.

// Declare and inject a Provider class for an API Dependency called Cnr inside a namespace prefix intns.
@Autowired
private CnrApiIntns cnr;  

// retrieve an offer from CNR API.
ResponseEntity<Offer> offer = cnr.adjustOffer(customerNeedId,offerId);

Event producer service

Publish domain events to Kafka topics.

Each event is assigned to a Kafka topic and the event producer service can be used by solution engineers to publish events.

// Declare Event Producer Service
@Autowired
private EventProducerService eventProdcuer;

// Declare entity builder
@Autowired
private EntityBuilder entityBuilder;  

// Create payload entity
SuccessEventPayload payloadEntity = entityBuilder.getCc().successEventPayload().build();

// Create domain event
SuccessEvent event = eventBuilder.getCc().successEvent().setPayload(payloadEntity).build();

// Publish a domain event.
eventProducer.publish(event);
Tip: Dedicated event publish function(s) are available within project stubs (services, commands, agents) that are configured to publish events.

Distributed tracing

  • To provide distributed tracing among your different service projects, Spring cloud sleuth is already a dependency of your service project.

  • All incoming and outgoing requests are tracked by default.

  • To create extra spans of your incoming request flow, you can easily use @NewSpan annotation to annotate your methods where you want a tracing span to be created.

  • By default all generated services base classes are annotated with @NewSpan.

Logging

Logging can be using slf4j logger factory to create a logger instance.

slf4j provides the info,warn,debug,trace log levels.

// Add imports for slf4j
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Create logger instance
private static Logger log = LoggerFactory.getLogger(CardRECommand.class);

// logging examples
log.info("Saved a new Credit Card Root Entity with Id {}", cardRootEntity.getId());