What is the proper way of handling command that need state from query model

So suppose I want to create e-commerce system. there will be OrderProductCommand which has productId and couponCode. I need to somehow retrieve additional data from query model (price and discount). At first, I tried to use queryGateway like this.

@Aggregate
class UserAggregate {
  var balance = BigDecimal.ZERO
  
  @CommandHandler
  fun handle(cmd: OrderProductCommand, queryGateway: QueryGateway){
    val product = queryGateway.query(FindProductById(cmd.productId), ResponseTypeOf(Coupon::class.java)).get()
    val discount = queryGateway.query(FindCouponByCode(cmd.couponCode), ResponseTypeOf(Coupon::class.java)).get()
    if (product.price*(1-discount) >= balance){
      throw IllegalArgumentException("Insufficient balance")
    }
    AggregateLifecycle.apply(...)
  }
}

but then I read this post is running a query… and I know that this implementation is not good practice I should use Saga which axon already provided instead. So I try to find an example of how to do it properly on this example AxonTrader,but there’s no usage of query gateway. So I don’t know what’s the proper way of doing it could anyone give me an example. one more question, I wonder if I don’t want to use Saga can I handle it inside of a controller like this instead.

@Controller
class UserController {

  @Autowired
  private lateinit var queryGateway: QueryGateway

  @Autowired
  private lateinit var commandGateway: CommandGateway

  data class OrderProductDTO(
    val orderId: String,
    val productId: String,
    // this provide by admin
    val couponCode: String
  )

  data class OrderProductCommand(
    val orderId: String,
    val price: BigDecimal,
    // this provide by admin
    val discount: Int
  )

  @PostMapping
  fun purchaseProduct(dto: OrderProductDTO): CompletableFuture<String> {
    val monoProduct = Mono.fromFuture(queryGateway.query(FindProductById(cmd.productId), ResponseTypeOf(Coupon::class.java)))
    val monoCoupon = Mono.fromFuture(queryGateway.query(FindProductById(cmd.productId), ResponseTypeOf(Coupon::class.java)))
    return Mono.zip(monoProduct, monoCoupon)
      .flatMap { (price, discount) -> OrderProductCommand(orderId = dto.orderId, price = price, discount = discount) }
      .toFuture()
  }
}

or it’s also not a good practice I should always rely on Saga.

Hi Lincoln, welcome to our forum!

When it comes to enriching the data, I will opt to perform the enrichment either (1) before dispatching the command or (2) after the publication of the event.

The former would justify the UserController that you have shared.
Given the scenario, I believe this is the most feasible approach for you at the moment.

If you prefer to react to the event to enrich the state later, you could take a look at an Event Handler.
Note that a Saga is not necessarily the solution in this case. A Saga should only be used to coordinate activity between 2 or more aggregate(-types). In your application, the Saga would go back and forth between one aggregate instance, so using the Saga capabilities of the framework would be overkill. Hence, a regular event handler would fit the bill better if you’d go for option 2.

The question that you should ask yourself here to deduce if you should go to option 1 or 2 is whether you require the “product” and “discount” information inside the aggregate. To deduce this, you should ask yourself whether you need that information as aggregate-state for validation or whether your event stream should record the “product” and “discount” until the end of time. If yes, enriching the command before dispatching would be the road to take, as it gives you the information early in the aggregate’s lifecycle. If no, then the state is likely only required for querying purposes. This would still open both options, by the way, but does provide extra flexibility for it to be present at a later stage.

I would also like to add that the Axon-Trader is super old and archived for a reason - it still uses Axon 3 =)

For a more recent project/sample, I would highly recommend our hotel-demo repository.

KR,

We are currently “kicking the tires” of Axon/AxonServer. We have been discussing how to best do something similar (we are not querying aggregates, but there are some “lookups” involved).

We are trying hard to design and code a DDD inspired system, where our domain and application code is not tied to external dependencies (but we are going for ES/CQRS in the design, so it matches nicely with axon and similar frameworks).

In the “UI” layer we receive web service calls and create commands with primitive fields (String, long etc.) - We would like to enrich those commands on their way towards the domain aggregates (mapping the primitives into proper domain types (like ItemId, ItemVersion and such), looking up complex (value) objects in other services, checking authz etc.)

We have discussed emitting an “application cmd” through the axon cmd gateway from the REST controller, and then have an command handler in the application layer that transforms the application cmd into a domain cmd and puts it into the axon cmd gateway targeting the aggregates.

Another option discussed is to emit the application command and then perform the enriching using the interceptors and just storing the pure domain command in the metadata (our axon aggregates just unwraps the domain command anyway and delegates it to corresponding the domain aggregates so we do not have to mess up our business logic with axon annotations and such).

Is it OK to “refine the cmds” on their way to the aggregates? And if so, what is the recommended way of doing so, our current guesses are:

  1. app cmd mapped to domain cmd (using handlers in the different layers)
    2.) app cmd with the domain cmd in metadata? (using interceptor mechanism on the app layer)
    3.) other options?

Are there any special implications for any of the strategies, that we need to be aware of?

Thanks!


Chris

Refining command messages is fine, IMO, granted that the use case serves the purpose.

After re-reading my previous comment, I feel I’ve neglected a small piece of info on the subject.

It’s not by definition wrong to validate data outside of the scope of the Aggregate instance when handling a command.
But, this model living outside the aggregate’s boundaries, its intent should be for command validation.

Differently put, it needs to be a part of the Command Model, not some section of a Query Model you happen to have too within the system.

Hence, refining the command with fields used for validation may or may not be feasible.
If the additional data doesn’t reside inside the system (read: it’s not part of the command model), then enriching beforehand safes on blocking-time of the aggregate.
If it is part of the command model, retrieving it once inside the aggregate should be fine.

@Chris_Poulsen, the types of enrichment you describe to me more sound like mapping one object to another.
Whenever your application crosses boundaries, I recommend performing mapping tasks like these.
Doing so will, for example, decouple your controller (in your case, the component using primitives) from your domain model.

However, whether I would put an “app cmd” inside a “domain cmd” is not entirely clear to me.
Mainly because I am not overly sure what you mean with the “app cmd.”
The boundary argument I just put up comes from the Bounded Context concept from DDD, by the way.

So, to paraphrase in short:

Bounded Context:
Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Keep the model strictly consistent within these bounds, but don't be distracted or confused by issues outside.

So, if you’re speaking about the language outside of your bounded context when talking about the “app cmd,” then I’d wager we’re on the same page. :slight_smile:

FYI, awesome to hear you and your team are giving it a go, Chirs!
Be sure to contact us at AxonIQ if you require a more direct form of support or training.
We’d gladly help.

With that said, I hope the above gives some guidance!

We are trying to map commands between layers, I’ll try to elaborate.

For example we have the following app cmd that the UI layer (REST) can request:

package application;

import java.io.Serializable;
import java.util.Objects;

public record StartGuideCmd(String guideId, long guideVersion) implements Serializable {
    public StartGuideCmd {
        Objects.requireNonNull(guideId);
    }
}

We would like to transform that into a domain cmd before it hits the aggregates:

package domain.session;

import domain.guide.model.GuideReference;

import java.util.Objects;

public record CreateGuideSession(GuideReference guideReference) implements GuideSessionCommand {
    public CreateGuideSession {
        Objects.requireNonNull(guideReference);
    }
}

Both commands obviously refers to the same operation, but the “CreateGuideSession” ensures that the Guide exists, is visible to the user etc. (These “concerns” are not something we want the guide session to handle) - In this context the guide is just a value object.

We carry the CreateGuideSession command to the aggregate using an axon create command (adapter either with similar fields or the CreateGuideSession as a field - mostly mentioned for completeness) like:

package axon.service.model;

import domain.guide.model.GuideReference;
import domain.guide.session.CreateGuideSession;

import java.io.Serializable;
import java.util.Objects;

public record AxonCreateCmd(GuideReference guideReference) implements Serializable {
    public AxonCreateCmd {
        Objects.requireNonNull(guideReference);
    }

    public static AxonCreateCmd from(CreateGuideSession create) {
        return new AxonCreateCmd(create.guideReference());
    }

    public CreateGuideSession toDomainCommand() {
        return new CreateGuideSession(guideReference);
    }
}

PS. this axon adapter has no axon specifics, but that is due to the fact that aggregate creation does not target an existing aggregate - other axon command adapters include `@TargetAggregateIdentifier.

The axon aggregate maps from axon messages to the domain versions and drives the “pure” aggregate using those domain cmds/events.

There is some boilerplate around the mapping between layers, but at this point we are willing to perform some work in order to not get the pure Java code that closely matches the design mixed up with framework specifics etc.

The “command transformation” is roughly equivalent to what would happen in application services in a more traditional DDD setup I think - but we are trying to keep application services and domain in separate boxes and communicate using messages.

So the question is probably whether it is OK to transform the application cmd to a domain cmd after putting it on the cmd bus (having the app @Commandhandler emit another, refined command) - or maybe transform it using interceptors. Another option in our case is to just put the “domain cmd” into metadata (instead of changing cmd type) and pull out the domain cmd in the axon aggregate and delegate it to the pure domain aggregate.

We have also discussed encapsulating the cmd transformation with a CommandFactory-like thing that does the work - so we actually have the finished command transformation/enrichment before we put anything on the command bus. But that case will probably expose the domain stuff to the controllers.

Gotcha, this is going outside of the scope of the original question, but I’ll answer it regardless.

Axon discovers Command Handlers based on the commandName. By default, this is the fully qualified class name of the first parameter of an @CommandHandler annotated method.
So, let’s assume we have the following command handler:

@CommandHandler
public void handle(AxonCreateCmd command) {
    // business validation and potential event publication
}

Axon will register this method as a command handler capable of dealing with the AxonCreateCmd.
So, if you would dispatch a CreateGuideSession or StartGuideCmd, Axon would not find a command handler for it.
As stated, the only option you have to switch somewhat is by changing the commandName on the @CommandHandler and when dispatching the command,
Interceptors wouldn’t work here either, as the command handling method is searched before any interceptors are invoked.

If the sole intent of this endeavor is to completely separate Axon from your domain, then I think there are different means of achieving this.
You can provide your custom annotations (thus, not Framework specific) and apply them to your components. After that, you can configure Axon Framework to utilize those annotations instead of the defaults.

Doing so eliminates Axon’s logic from your domain, keeping it pure.
A contributor has made a very extensive sample application that also does this, actually.
Here’s his walkthrough of that project. I highly recommend checking it out.

To be honest with you, we (read: AxonIQ) tend to stick to the pragmatic side instead of the “pure” side of building out your domain objects.
Indeed there’s a lot to be said about sticking to the theory to the max.
There’s nothing inherently wrong with that.

But especially the message mapping argument you’re sharing here seems going the stretch, in my honest opinion.
The commands, events, and queries should be regarded as the core API of your bounded context. Any service belonging to that bounded context should simply be allowed to understand that language entirely.
Hence, setting up such a mapping structure, to me, sounds like increasing the number of bounded contexts a little too much.

Granted, I am not the domain expert on your team.
So the above is just my two cents on what you’re describing.
I do hope it helps you further, @Chris_Poulsen.

Thank you for the quick reply!

We’re still just messing around, trying to get some feel for axon framework and hopefully land on a reasonable solution (that process includes visiting the options from no-mapping of concepts to full-mapping of concepts across layers, the example above is the from the latter). For the experiments we have (re-)modelled a tiny corner of a rather large system that have accumulated functionality over 20 years - and is using that for evaluation. Given the amount of functionality we need to clean up and bring some order/design to, we’re a bit wary about getting things messed up (again) by not using strict layering / contexts.

We have a working example of a controller putting an app cmd on the command bus, that is then being handled by a @CommandHandler that maps and emits the domain version of the cmd to the command bus, which in turn is handled by the aggregates. We realize that a command handler that does not map from cmd → event(s) is not the norm, hence the experiments around other strategies for performing the cmd transformation as it passes through layers.
I’m having a hard time locating documentation that describes the “world” around the message bus in axon. What does the FW / axon server provide when it comes to things like delivery guarantees, which rules/assumptions does the framework/server have for our message handlers (do we have “exactly once delivery” or are we forced to ensure idempotent behavior as the bus only guarantees “at least once delivery” ) and so on, but I think that will be material for a separate topic - unless someone can provide a link to a page I missed.

I’ll take a look at the example app you linked, thanks!

Sure thing Chris!
I’m happy to give my insights on the subject.

Investigating your options is always a good step to take.
Most important with any tech if you ask me, is that you and your team come to a format that works for you.

Indeed a separate topic! :wink:
But, I’m going to give some insights regardless (based on what you’ve shared).

It is recommended to make any command handler idempotent.
This is not so much an Axon Framework or Server-specific thing, but rather a distributed computing argument I’m putting up.
Note that Framework, Server, nor Axon’s Extension all don’t intent to have duplicate delivery.

But, an end-user of your system could just as well “bash the button” multiple times because a response doesn’t come in quick enough.
Or you have configured retry mechanisms reacting on timeouts, whereas network latency caused the (command) message to come in regardless.

Anyhow, any material we have on this, is all situated in the Reference Guide, albeit not placed on one convenient page for you.
Note that at all times you can also contact AxonIQ directly.
Our engineers (who knows, might just be me) are more than willing to have a chat with you to deduce how to proceed.