@Bean public SagaManager<CreateOrderSagaData> createOrderSagaManager(Saga<CreateOrderSagaData> saga) { return new SagaManagerImpl<>(saga); }
@Override public SagaInstance create(Data sagaData) { return create(sagaData, Optional.empty()); }
private void updateEventInstanceSubscriptions(Data sagaData, String sagaId, String stateName) { List<EventClassAndAggregateId> instanceEvents = getStateDefinition().findEventHandlers(saga, stateName, sagaData); aggregateInstanceSubscriptionsDAO.update(getSagaType(), sagaId, instanceEvents); }
private void maybePerformEndStateActions(String sagaId, SagaInstance sagaInstance, Optional<String> possibleNewState) { possibleNewState.ifPresent(newState -> { if (getStateDefinition().isEndState(newState)) { performEndStateActions(sagaId, sagaInstance); } }); }
private void handleReply(Message message) { if (!isReplyForThisSagaType(message)) return; if (isDuplicateReply(messageId, sagaType, sagaId)) return; Optional<ReplyClassAndHandler> replyHandler = getStateDefinition() .findReplyHandler(saga, sagaInstance, currentState, sagaData, requestId, message); publishEvents(sagaId, actions.getEventsToPublish(), actions.getUpdatedState()); maybeUpdateState(sagaInstance, possibleNewState); maybePerformEndStateActions(sagaId, sagaInstance, possibleNewState); updateEnlistedAggregates(sagaId, actions.getEnlistedAggregates()); sagaInstance.setLastRequestId(sendCommands(sagaId, commands)); updateEventInstanceSubscriptions(sagaData, sagaId, sagaInstance.getStateName());
@Override public SagaInstance create(Data sagaData, Optional<String> resource) { SagaInstance sagaInstance = new SagaInstance(getSagaType(), null, "????", null, SagaDataSerde.serializeSagaData(sagaData), new HashSet<>()); sagaInstanceRepository.save(sagaInstance); String sagaId = sagaInstance.getId(); resource.ifPresent( r -> Assert.isTrue(sagaLockManager.claimLock(getSagaType(), sagaId, r), "Cannot claim lock for resource")); SagaActions<Data> actions = getStateDefinition().getStartingHandler().get().apply(sagaData); List<CommandWithDestination> commands = actions.getCommands(); sagaData = actions.getUpdatedSagaData().orElse(sagaData); sagaInstance.setLastRequestId(sendCommands(sagaId, commands)); sagaInstance.setSerializedSagaData(SagaDataSerde.serializeSagaData(sagaData)); publishEvents(sagaId, actions.getEventsToPublish(), actions.getUpdatedState()); Optional<String> possibleNewState = actions.getUpdatedState(); maybeUpdateState(sagaInstance, possibleNewState); maybePerformEndStateActions(sagaId, sagaInstance, possibleNewState); sagaInstanceRepository.update(sagaInstance); updateEnlistedAggregates(sagaId, actions.getEnlistedAggregates()); updateEventInstanceSubscriptions(sagaData, sagaId, sagaInstance.getStateName()); return sagaInstance; }
public <T> SagaUnitTestSupport saga(Saga<T> saga, T sagaData) { SagaInstanceRepository sagaInstanceRepository = new SagaInstanceRepository() { private SagaInstance sagaInstance; @Override public void save(SagaInstance sagaInstance) { sagaInstance.setId(SAGA_ID); this.sagaInstance = sagaInstance; } @Override public SagaInstance find(String sagaType, String sagaId) { return sagaInstance; } @Override public void update(SagaInstance sagaInstance) { this.sagaInstance = sagaInstance; } }; CommandProducerImpl commandProducer = new CommandProducerImpl((destination, message) -> { String id = genId(); message.getHeaders().put(Message.ID, id); sentCommands.add(new MessageWithDestination(destination, message)); }, new DefaultChannelMapping(Collections.emptyMap())); SagaCommandProducer sagaCommandProducer = new SagaCommandProducer(commandProducer); MessageConsumer messageConsumer = null; SagaLockManager sagaLockManager = null; sagaManager = new SagaManagerImpl<>(saga, sagaInstanceRepository, commandProducer, messageConsumer, new DefaultChannelMapping(Collections.emptyMap()), sagaLockManager, sagaCommandProducer); sagaManager.create(sagaData); return this; }
private void processActions(String sagaId, SagaInstance sagaInstance, Data sagaData, SagaActions<Data> actions) { String lastRequestId = sagaCommandProducer.sendCommands(this.getSagaType(), sagaId, actions.getCommands(), this.makeSagaReplyChannel()); sagaInstance.setLastRequestId(lastRequestId); actions.getUpdatedState().ifPresent(sagaInstance::setStateName); sagaInstance.setSerializedSagaData(SagaDataSerde.serializeSagaData(actions.getUpdatedSagaData().orElse(sagaData))); if (actions.isEndState()) { performEndStateActions(sagaId, sagaInstance, actions.isCompensating(), sagaData); } sagaInstanceRepository.update(sagaInstance); }
private void performEndStateActions(String sagaId, SagaInstance sagaInstance) { for (DestinationAndResource dr : sagaInstance.getDestinationsAndResources()) { Map<String, String> headers = new HashMap<>(); headers.put(SagaCommandHeaders.SAGA_ID, sagaId); headers.put(SagaCommandHeaders.SAGA_TYPE, getSagaType()); // FTGO SagaCommandHandler failed without this but the OrdersAndCustomersIntegrationTest was fine?!? commandProducer.send(dr.getDestination(), dr.getResource(), new SagaUnlockCommand(), makeSagaReplyChannel(), headers); } }
@Override public SagaInstance create(Data sagaData, Optional<String> resource) { SagaInstance sagaInstance = new SagaInstance(getSagaType(), null, "????", null, SagaDataSerde.serializeSagaData(sagaData), new HashSet<>()); sagaInstanceRepository.save(sagaInstance); String sagaId = sagaInstance.getId(); resource.ifPresent( r -> Assert.isTrue(sagaLockManager.claimLock(getSagaType(), sagaId, r), "Cannot claim lock for resource")); SagaActions<Data> actions = getStateDefinition().start(sagaData); processActions(sagaId, sagaInstance, sagaData, actions); return sagaInstance; }
private void handleReply(Message message) { if (!isReplyForThisSagaType(message)) return; logger.debug("Handle reply: {}", message); String sagaId = message.getRequiredHeader(SagaReplyHeaders.REPLY_SAGA_ID); String sagaType = message.getRequiredHeader(SagaReplyHeaders.REPLY_SAGA_TYPE); SagaInstance sagaInstance = sagaInstanceRepository.find(sagaType, sagaId); Data sagaData = SagaDataSerde.deserializeSagaData(sagaInstance.getSerializedSagaData()); message.getHeader(SagaReplyHeaders.REPLY_LOCKED).ifPresent(lockedTarget -> { String destination = message.getRequiredHeader(CommandMessageHeaders.inReply(CommandMessageHeaders.DESTINATION)); sagaInstance.addDestinationsAndResources(singleton(new DestinationAndResource(destination, lockedTarget))); }); String currentState = sagaInstance.getStateName(); logger.info("Current state={}", currentState); SagaActions<Data> actions = getStateDefinition().handleReply(currentState, sagaData, message); logger.info("Handled reply. Sending commands {}", actions.getCommands()); processActions(sagaId, sagaInstance, sagaData, actions); }
private String makeSagaReplyChannel() { return getSagaType() + "-reply"; }
public void handleMessage(Message message) { logger.debug("handle message invoked {}", message); if (message.hasHeader(SagaReplyHeaders.REPLY_SAGA_ID)) { handleReply(message); } else if (message.hasHeader(EventMessageHeaders.EVENT_TYPE)) { String aggregateType = message.getRequiredHeader(EventMessageHeaders.AGGREGATE_TYPE); String aggregateId = message.getRequiredHeader(Message.PARTITION_ID); String eventType = message.getRequiredHeader(EventMessageHeaders.EVENT_TYPE); // TODO query the saga event routing table: (at, aId, et) -> [(sagaType, sagaId)] for (SagaTypeAndId sagaTypeAndId : aggregateInstanceSubscriptionsDAO.findSagas(aggregateType, aggregateId, eventType)) { handleAggregateInstanceEvent(sagaTypeAndId.getSagaType(), sagaTypeAndId.getSagaId(), message, aggregateType, aggregateId, eventType); } ; } else { logger.warn("Handle message doesn't know what to do with: {} ", message); } }
@PostConstruct public void subscribeToReplyChannel() { // TODO subscribe to events that trigger the creation of a saga messageConsumer.subscribe(saga.getClass().getName() + "-consumer", singleton(channelMapping.transform(makeSagaReplyChannel())), this::handleMessage); }
private void publishEvents(String sagaId, Set<EventToPublish> eventsToPublish, Optional<String> updatedState) { Set<EnlistedAggregate> elas = emptySet(); //enlistedAggregatesDao.findEnlistedAggregates(sagaId); boolean isEndState = updatedState.filter(s -> getStateDefinition().isEndState(s)).isPresent(); // TODO - an alternative model is to 'onResume()' based on a SagaCompletedEvent being published // Domain object doesn't have to publish event // What about automatically suspending if there is already a saga-in-progress? and delay the state check until much later for (EventToPublish event : eventsToPublish) { Map<String, String> headers = new HashMap<>(); if (isEndState) { Set<String> sagaIds = enlistedAggregatesDao.findSagas(event.getAggregateType(), event.getAggregateId()); sagaIds.remove(sagaId); headers.put("participating-saga-ids", sagaIds.stream().collect(joining(","))); } domainEventPublisher.publish(event.getAggregateType().getName(), event.getAggregateId(), headers, event.getDomainEvents()); } }
public void handleMessage(Message message) { logger.debug("handle message invoked {}", message); if (message.hasHeader(SagaReplyHeaders.REPLY_SAGA_ID)) { handleReply(message); } else { logger.warn("Handle message doesn't know what to do with: {} ", message); } }
private void performEndStateActions(String sagaId, SagaInstance sagaInstance, boolean compensating, Data sagaData) { for (DestinationAndResource dr : sagaInstance.getDestinationsAndResources()) { Map<String, String> headers = new HashMap<>(); headers.put(SagaCommandHeaders.SAGA_ID, sagaId); headers.put(SagaCommandHeaders.SAGA_TYPE, getSagaType()); // FTGO SagaCommandHandler failed without this but the OrdersAndCustomersIntegrationTest was fine?!? commandProducer.send(dr.getDestination(), dr.getResource(), new SagaUnlockCommand(), makeSagaReplyChannel(), headers); } if (compensating) saga.onSagaRolledBack(sagaId, sagaData); else saga.onSagaCompletedSuccessfully(sagaId, sagaData); }
private String makeSagaReplyChannel() { return getSagaType() + "-reply"; }
@PostConstruct public void subscribeToReplyChannel() { messageConsumer.subscribe(saga.getSagaType() + "-consumer", singleton(channelMapping.transform(makeSagaReplyChannel())), this::handleMessage); }
private void handleAggregateInstanceEvent(String sagaType, String sagaId, Message message, String aggregateType, String aggregateId, String eventType) { System.out.println("Got handleAggregateInstanceEvent: " + message + ", type=" + sagaType + ", instance=" + sagaId); SagaInstanceData<Data> sagaInstanceAndData = sagaInstanceRepository.findWithData(sagaType, sagaId); SagaInstance sagaInstance = sagaInstanceAndData.getSagaInstance(); Data sagaData = sagaInstanceAndData.getSagaData(); String currentState = sagaInstance.getStateName(); logger.info("Current state={}", currentState); Optional<SagaEventHandler<Data>> eventHandler = getStateDefinition().findEventHandler(saga, currentState, sagaData, aggregateType, Long.parseLong(aggregateId), eventType); if (!eventHandler.isPresent()) { logger.error("No event handler for: {}", message); return; } logger.info("Invoking event handler for {}", message); SagaActions<Data> actions = eventHandler.get().getAction().apply(sagaData, new DomainEventEnvelopeImpl<>(null, null, null, null, null)); // TOOD // TODO - doesn't this do something??? Commands sagaInstance.setSerializedSagaData(SagaDataSerde.serializeSagaData(sagaData)); sagaInstanceRepository.update(sagaInstance); }