Ordered Events – How to Design for an Ordered List of Unrelated Events

api-designdesign

This is a bit of an invented example but I think it best illustrates my question: Say I'm creating a chess replay event API. Say I have a lot of different "events" I want to keep track of, in a specified order. Here might be some examples:

  1. A move event — this contains the previous and new square.

  2. A timer event — this contains the timestamp that the timer was toggled between players

  3. A chat message event — this contains the player ID, the message and time sent

…etc. The point is that the data model for each event is very different — there isn't much of a common interface.

I want to design an API that can store and expose essentially a List<Event> to a client who can choose to process these different events as they wish. We don't know what clients will do with this information: Perhaps one client may need to do text analysis on the ChatMessageEvents, and one may consume and replays these events in the UI. The challenge is that ordering between events must be preserved, so I can't separate by methods like getMoveEvents and getTimerEvents since a TimerEvent can happen between move events and the client may need that information.

I could expose a visitor to allow clients to handle each event type differently in the list, but I'm wondering if there's a better way to handle a situation like this.

Edit: I want to design this with one main priority: provide clients with an easy and flexible way to iterate through these events. In an ideal scenario, I would envision the end user writing handlers to the event types they care about, and then be able to iterate through without casting based on the runtime type.

Best Answer

I am under the strong impression you are overthinking this.

The challenge is that ordering between events must be preserved, so I can't separate by methods like getMoveEvents and getTimerEvents

Then simply don't offer such methods in your API. Let the client filter out the events they need, and do not implement anything in your API which could become error prone.

I could expose a visitor to allow clients to handle each event type differently in the list

This sounds overengineered. You described the requirement as getting something like a List<Event>, containing recorded events. For this, a simple method List<Event> getEvents() would be totally sufficient (maybe an IEnumerable<Event> would be enough). For reasons of efficiency, it may be necessary to offer some methods for restricting the result set to certain conditions.

but I'm wondering if there's a better way to handle a situation like this

Asking for a "better" (or "best", or "correct") approach is way too unspecific when you don't know any criteria for what you actually mean by "better". But how do find criteria for what is "better"? The only reliable way I know for this problem is:

Define some typical use cases for your API!

  • Do this in code. Write down a short function which tries to use your API, solving a real problem you know for sure the clients will encounter (even if the API does not exists or is not implemented yet).

    It may turn out the client will need something like a property to distinguish event types. It may turn out the client needs something to get only the events from the last hour, or the last 100 events, since providing him always a full copy of all former events may not be effcient enough. It may turn out the client needs to get a notification whenever a new event is created.

    You will only be able to decide this when you develop a clear idea of the context in which your API will be used.

  • If you add some code to this function which verifies the API's result, and place this code into a the context of a unit testing framework, then you are doing "Test Driven Development"

  • But even if you don't want to use TDD or don't like TDD, it is best to approach this from the client's perspective.

  • Don't add anything to your API where you have doubts if there will ever be a use case for. Chances are high noone will ever need that kind of function.

If you don't know enough about the use cases of the API to use this approach, you will probably do some more requirements analysis first - and that is something we cannot do for you.

Let me write something to your final edit, where you wrote

and then be able to iterate through without casting based on the runtime type.

Casting based on the runtime type isn't necessarily an issue. It becomes only a problem when it makes extensions to the Event class hierarchy harder, because existing Client code would be forced to change with each extension.

For example, let's say there is client code handling all chat events by a type test plus a cast for ChatEvent. If a new event type is added which is not a chat event, existing code will still work. If a new chat-like event is added, as a derivation of ChatEvent, existing code will also still work as long as the ChatEvent type conforms to the LSP. For specific chat events, polymorphism can be used inside the ChatEvent part of the inheritance tree.

So instead of avoiding type tests and casts superstitiously under all circumstances, because you have read in a text book "this is generally bad", reflect why and when this really causes any problems. And as I wrote above, writing some client code for some real use cases will help you to get a better understanding for this. This will allow you also to validate what will happen when your list of events get extended afterwards.

Related Topic