CreateOrUpdate command for Aggregate

Hello,

On my project we’re using Axon Server 4.2.
I’m implementing next use case:
N microservices on startup are calling using Command another one microservice(C-service) in order to send some configuration data. This data is registered as Aggregate in C-services.
The issue here is that N microservices are running in K8S so can have M running instances for same microservice. Each of this instance will send same configuration data to C-Service.
So C-Service will consume many times same Command from one type of microservice.

Is it possible to implement some thing like CreateOrUpdate Command which will Create new record for Aggregate if it does not all ready exists or Update existing record in Aggregate.

Or this kind of modelling does not follow DDD? What is the correct way in this case?

Stas

Hi Stanislav,

yes it is possible. Imagine you have three commands: CreateCommand, UpdateCommand and finally CreateOrUpdateCommand. Now write a CommandHandler and put it into a dedicated Component (not the aggregate).
The idea is to inject the AggregateRepository (in my example an event-souring repository), check if you can load the Aggregate, create if not, load if exists and then apply the Update command.

Here is how it looks like in Kotlin (Java or Scala should be straight forward):

@Component
class CreateOrUpdateCommandHandler {

  @Autowired
  private lateinit var eventSourcingRepository: EventSourcingRepository<DataEntryAggregate>

  @CommandHandler
  fun createOrUpdate(command: CreateOrUpdateDataEntryCommand) {

    loadAggregate(command.dataIdentity).ifPresentOrElse(
      presentConsumer = { aggregate ->
        aggregate.invoke {
          it.handle(
            UpdateDataEntryCommand(
              dataEntry = command.dataEntry
            )
          )
        }
      },
      missingCallback = {
        eventSourcingRepository.newInstance {
          DataEntryAggregate(
            CreateDataEntryCommand(
              dataEntry = command.dataEntry
            )
          )
        }
      }
    )
  }

  private fun loadAggregate(id: String): Optional<Aggregate<DataEntryAggregate>> =
    try {
      Optional.of(eventSourcingRepository.load(id))
    } catch (e: AggregateNotFoundException) {
      Optional.empty()
    }
}



If you want to see how it is used in Context, have a look here: https://github.com/holunda-io/camunda-bpm-taskpool/blob/develop/datapool/datapool-core/src/main/kotlin/io/holunda/camunda/datapool/core/business/CreateOrUpdateCommandHandler.kt

Hope this helps,

Kind regards,

Simon

Hi Simon,

This is exactly my case, thanks for great support!

Stanislav

In that case, how do you deal with the framework restriction/convention that the “create” handler must return the aggregateIdentifier while other handlers should not return aggregate data?

It’s not so much a restriction or requirement by the framework. Whatever the value is that a handler returns, is what the sender receives as result of the command. The only exception is annotated constructors. The value returned by those “methods” is the aggregate itself. Since we very highly recommend never returning an aggregate or entity, we return the identifier instead. If you create an “external command handler” (a handler not placed directly on an entity), then you can return whatever you want.

Cheers,