Search code examples
eventsdomain-driven-designevent-sourcingsagaprocess-management

Should this Process Manager state be persisted?


I am developing an event-sourced Electric Vehicle Charging Station Management System, which is connected to several Charging Stations. In this domain, I've come up with an aggregate for the Charging Station, which includes the internal state of the Charging Station(whether it is connected, the internal state of its Connectors).

The commands I can issue to a Station aggregate are:

  • UnlockConnector, which emits StationConnectorUnlocked

  • StopConnectorEnergyFlow, which emits StationConnectorEnergyFlowStopped

And I've come up with another aggregate which represents the Charging Session, the interaction between a User and a Charging Station's Connector. The creation of a Charging Session is coupled with these events, i.e. if the Connector has been unlocked by a user, a session was created, if the Connector's energy flow has stopped, the Charging Session has finished.

I've added a Process Manager that listens to the events:

  • StationConnectorUnlocked(stationID, connectorID) -> SessionCreated(new uuid())

  • StationConnectorEnergyFlowStopped(stationID, connectorID) -> SessionFinished(id???)

for the first event, it's pretty straight-forward to create the session. However for the latter, it must know the sessionID of the ongoing session happening on the Connector(connectorID) of the Station(stationID), so it can update the session.

I can't simply implement a GetSessionByConnectorID function as I'm implementing an event-sourced system, and the only way I can get the event stream of a session, is by its ID, not by its ConnectorID(because I only know the session's ConnectorID when I hydrate it back), so I don't see how I could implement the GetSessionByConnectorID function.

So, the process manager has some internal in-memory state((stationID, connectorID) -> (sessionID)), to keep track of the sessionID. However, as it is in-memory, as soon as the process manager crashes, I've lost the association between the (stationID, connectorID) <-> (sessionID) and I can no longer respond properly to the ConnectorEnergyFlowStopped event.

session-flow

How should I handle that? Should I persist the Process Manager state? Should I persist it with Process Manager events(which would be awkward since, it does not correlate nicely with the Ubiquitous Language, i'm thinking SessionProcessManagerReceivedStationConnectorUnlockedEvent-awkward-level)

Edit - new thought

I thought about something else, which would be to remove the internal Process Manager state, and put the association of the (stationID, connectorID) <-> (sessionID), inside of the Station aggregate. That would, unfortunately, mean a higher coupling(the Station must know when a session is Created, and how to generate its ID), but I think it could be simpler(as the Station's events are persisted). So the Station would emit the session-related events, with the sessionID inside of them:

  • StationConnectorUnlocked(stationID, connectorID, sessionID) -> SessionCreated(sessionID)

  • StationConnectorEnergyFlowStopped(stationID, connectorID, sessionID) -> SessionFinished(sessionID)

However, it does seem a bit odd to mix the two things, even if it's just the ID of the session.


Solution

  • I can see quite a few ways of solving this but I am not familiar with your domain, so just throwing ideas here:

    • Use the process manager. Process managers handle long-running processes and need to persist the state _by definitions. Message-driven process managers usually don't need to even run between messages, so they process events, issue commands and shut down until the next message. Or finalize. That's, as I understand, was the initial question.

    • Issue session UUID from the station, for each session. Then, the station would know the session ID and there's no need for process managers.

    • Use the query model. Project the session started event with all the information you need. Query it when the flow of energy stops to find out an ongoing session for a given station and connector, you might not even need the user id. Project sessionClosed event and delete the read model.

    If possible, I'd go with the second one. The last one would be second in my list and the process manager would be the last resort due to the associated complexity.