Implement Commands

Command base

  • For each root entity there will be an abstract class Command Base generated in the SDK.

  • The Command Base provides access to the repository, the entity builder, the event builder and the event producer.

  • The Command Base contains abstract methods, one method for each modelled command.

  • These Commands method needs to be implemented in the generated implementation file for the Root Entity Commands.

Command inheritance

Command inheritance is supported, Root Entity will inherit its parent(s) commands.

  • By default, if your root entity inherits commands from parent entities, By default the generated abstract command class will call these parent command(s) directly.

  • In the command generated stub, user would have the possibility to either

    1. Override the implementation of these command(s) that are coming from its parent(s).
    2. Don't override the implementation by deleting that command stub method / calling the parent implementation via super key word.

Factory commands

The main purpose of a factory command is to set values to the properties of a root entity and then persist those changes to the database, thus, creating a new instance.

  • Factory commands will return Root Entity as a return type

  • Factory commands take an Input Entity as the only parameter, if they have been modelled with one.

Example

The factory command to create an instance of a root entity called MasterCard is the following:

    @Service
    public class MasterCardCommand extends MasterCardCommandBase {

      @Override
      public void createCreditCard(MasterCard instance, CreateCreditCardInput createCreditCardInput) {
      
        // Fill instance with values from input entity
        instance.setBankName(createCreditCardInput.getBankName());
        
        // save entity to repository
        repo.cc.masterCard.save(instance);
      }
    }

Instance commands

Instance commands usually hold logic to modify the state of a root entity instance and persist the updated state in the database. The instance that can be modified as well as the input of the command is provided automatically.

  • Instance commands take an instance of Root Entity as the first parameter named instance and an Input Entity if they have been modelled with one.

  • Instance commands always return void.

  • Instance commands operate against the root entity instance that is passed to it.

Example

The implementation of an instance command called ActivateCard which manipulates the state of an instance of the root entity MasterCard is shown below:

    @Service
    public class MasterCardCommand extends MasterCardCommandBase {

      @Override
      public void activateCard(MasterCard instance) {
      
        // Change root entity data
        instance.setActive(true);
        
        // Save to repository
        repo.cc.masterCard.save(instance);
      }

Business errors

  • If a command is modelled with business errors, it will be added as method throws declaration.

Events

  • If a root entity command is modelled with business events, for each event there will be four inherited methods in the Command Base class to publish the event with the following signatures.

    • Event only
    • Event and messageKey
    • Event and messageHeaders
    • Event, messageKey and messageHeaders
  • It is also possible to publish events with any publish method mentioned above using EventProducerService directly.

Implementation Example

Example of root entity command implementation file.

Root Entity Card has a factory command createCreditCard and an instance command activateCreditCard.

//... imports
import myproj.sdk.domain.schemas.SchemaGroup.BalanceCheckedSchema;

@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);
	}

	// Example of a factory command named createCreditCard that takes CreditCard as an input entity
	@Override
	public Card createCreditCard(CreditCard creditCard) throws CreditCardCreationError {

		log.info("CardCommands.createCreditCard()");

		// Use Domain Entity Builder from base class to create an instance of root entity
		Card cardRootEntity = this.entityBuilder.getCc().getCard().build();

		// Use card details data from the input entity
		cardRootEntity.setCardDetails(creditCard);
		cardRootEntity.setIsActive(false);

		// If an Exception occurred while saving the new card, catch and throw relevant business error
		try {
			cardRootEntity = this.repo.getCc().getCard().save(cardRootEntity);
		} catch(Exception e) {
			log.error("Could not create a new credit card", e);
			String errorMessage = String.format("Credit card creation failure, %s" , e.getMessag());
			throw new CreditCardCreationError(errorMessage);
		}

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

		// Publish event, if the event is using a schema from the schema registry as payload
		BalanceCheckedEvent schemaEvent = this.eventBuilder.getCc().getBalanceCheckedEvent().build();

		// Create the payload for the event
		BalanceCheckedSchema schema = new BalanceCheckedSchema();
		schema.setProperty1('value');
		schemaEvent.setPayload(schema);

		// Publish the event
		this.publishBalanceCheckedEvent(schemaEvent);

		/**
		* Create an event and set payload entity, if the event is using an entity as payload
		* only available for event support 1.0
		* @deprecated use schemas from the schema registry instead
		*/
		CreditCardCreated event = this.eventBuilder.getCc().getCreditCardCreated().build();
		event.setPayload(creditCard);

		// Publish event using base class inherited method
		this.publishCreditCardCreated(event);

		log.info("Published New Credit Card Event Successfully");
		
		return cardRootEntity;
	}

	// Example of an instance named activateCreditCard that takes only the Card root entity.
	@Override
	public void activateCreditCard(Card instance) {

		log.info("CardCommands.activateCreditCard()");

		// Activate Card
		instance.setActive(true);

		// Update root entity 
		this.repo.getCc().getCard().save(instance);
		
		log.info("Activated Card with Id {}", instance.getId());
		
		return;
	}

	// Example of calling inherited command from parent and not overriding its implementation.
	@Override
	public void deleteCard(Card instance) {

		return super.deleteCard(instance);
	}
}

Example for the alternative ways to publish event from command.

//... imports
import myproj.sdk.domain.schemas.SchemaGroup.BalanceCheckedSchema;

@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 {

		// create event
		BalanceCheckedEvent schemaEvent = this.eventBuilder.getCc().getBalanceCheckedEvent().build();

		// Create the payload for the event
		BalanceCheckedSchema schema = new BalanceCheckedSchema();
		schema.setProperty1("value");
		schemaEvent.setPayload(schema);

		// Publish the event
		this.publishBalanceCheckedEvent(schemaEvent);

		// publish the event with custom messageKey
		this.publishBalanceCheckedEvent(schemaEvent, "customMessageKey");

		// publish the event with  messageHeaders
		HashMap<String, Object> map = new HashMap();
		map.put("headerKey", "headerValue");
		MessageHeaders headers = new MessageHeaders(map);
		this.publishBalanceCheckedEvent(schemaEvent, headers);

		// publish the event with messageKey and messageHeaders
		this.publishBalanceCheckedEvent(schemaEvent, "customMessageKey", headers);

		/**
			* Event 1.0 with entity payload
			* @deprecated use schemas from the schema registry instead
		*/
		CreditCardCreated entityEvent = this.eventBuilder.getCc().getCreditCardCreated().build();
		entityEvent.setPayload(creditCard);

		// Publish event using base class inherited method
		this.publishCreditCardCreated(event);

		// Publish the event with messageHeaders
		HashMap<String, Object> map = new HashMap();
		map.put("headerKey", "headerValue");
		MessageHeaders headers = new MessageHeaders(map);
		this.publishBalanceUpdatedEvent(entityEvent, headers);

		log.info("Published New Credit Card Event Successfully");
		
		return cardRootEntity;
	}
}
Warning:

Message key will be ignored in case of publishing event 1.0 (Entity payload).

Note:

If you are using the aggregate persistence functionality, please ensure that your code is compliant with the recommended restrictions of the used database. Consult MongoDB documentation for further information. For example: Because of the limitations of database operations and drivers, MongoDB does not recommend the use of timestamps, that are not within the year range of 0 - 9999, see Date and DateTime documentation.