Importing external event

Hi!

I have a use case, where I legitimately have to create an event using Event Gateway from an API call. The external system (which does not use Event Sourcing) notifies me that something has happened, so I translate it into an event. That system will retry its notification in some cases, so I have to ensure I don’t create duplicate events.

So, I found that I can wrap my event into a GenericDomainEventMessage specifying the aggregate id. And then I am able to find it using an EventStore, so I can throw “already exists”. Please, take a look at the code and tell if you see any issues/improvements as it does feel very hacky, but it may be fine because my use case is already hacky by itself and probably not expected from Axon perspective.

import lombok.RequiredArgsConstructor;
import org.axonframework.eventhandling.GenericDomainEventMessage;
import org.axonframework.eventhandling.gateway.EventGateway;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/documents")
public class DocumentController {

    private final EventGateway eventGateway;
    private final DocumentMapper mapper;
    private final EventStore eventStore;

    @PostMapping
    public void acceptNotificationDocumentCreated(@RequestBody DocumentCreateRequest documentDto) {
        DocumentCreatedEvent documentCreatedEvent = mapper.toDocumentCreatedEvent(documentDto);
        assertThatDocumentDoesNotExist(documentDto.getId());
        eventGateway.publish(new GenericDomainEventMessage<>(
                Document.class.getSimpleName(),
                documentDto.getId(),
                //have to use -1 because later down the chain this aggregate is "actually" created with 0 and we get a SQL error
                -1L,
                documentCreatedEvent
        ));
    }

    private void assertThatDocumentDoesNotExist(String id) {
        if (eventStore.lastSequenceNumberFor(id).isPresent()) {
            throw new DocumentException(ErrorType.DOCUMENT_ALREADY_EXISTS, "with id " + id);
        }
    }
}

Btw, does it make sense to use GenericTrackedDomainEventMessage in this case? Not sure how though

I think I also need to add locking for publishing with LockFactory

actually, I would have to use distributed locking for this…

actually, I don’t need locking because if I receive the same request twice at the same time, second request will fail on SQL insert but nothing bad will happen other than wrong http response, which is “fine”.

Hey Sam,

Although I get your hunch that the external system imposes an Event within your other system, I would still decouple those notions from one another.
The external system should be regarded as another Bounded Context instead.
This means that the context listening to the event should not rely on the used language of the external system.

Thus, that the external system states it’s an Event is fine, but that does not impose any constraints on the ingesting system.
I would rather states your DocumentController should function as a sort of Anti-Corruption Layer that ingests the Events, but changes it to a Command for the internal system.

The necessity for this stems from the fact you want said events to be part of an Aggregate’s lifecycle.
To publish an events as part of an Aggregate, you are inclined to do this from within your Aggregate, as you cannot guarantee the sequence number to be set correct for said Aggregate.

Hope this helps you out Sam!

Cheers,
Steven

Hi, Steven!

Thanks for the response, it does make sense to me actually. So, since I have to validate before publishing an event and also want to put that event under the aggregate, it does very much sound like a place for a command. Thanks, didn’t realize that until now :slight_smile: