The GitHub CQRS.NET project has some concrete examples of how you can create EventStores in several different technologies. At the time of this writing, SQL implements an implementation using Linq2SQL and an SQL schema , one for MongoDB , one for DocumentDB (CosmosDB if you're on Azure), and one using EventStore (as mentioned above). There is something else in Azure, like Table Storage and Blob storage, which is very similar to flat file storage.
I suppose the bottom line is that they all comply with one principle / contract. They all store information in one place / container / table, they use metadata to identify one event from another, and "just" keep the entire event as it was - in some cases serialized, as well as supporting technologies. Thus, depending on whether you choose a document database, a relational database, or even a simple file, there are several different ways to achieve the same intention of event storage (this is useful if you change your mind at any time and find that you need to migrate or support more than one storage technology).
As a project developer, I can share some views on some of the decisions we made.
Firstly, we found (even with unique UUIDs / GUIDs instead of integers) for many reasons, consecutive identifiers occur for strategic reasons, so just having an identifier was not unique enough for the key, so we combined our key column with the main identifier data / Type an object to create what should really (in the sense of your application) be a unique key. I know that some people say that you do not need to store it, but it will depend on whether you are in a new place or you need to coexist with existing systems.
We settled on one container / table / collection for ease of maintenance, but we played with a separate table for each entity / object. In practice, we found that this means that either the application needs "CREATE" permissions (which, generally speaking, is not a good idea ... as a rule, there are always exceptions / exceptions), or every time a new object / object appears or deployed, new storage containers / tables / collections should be made. We found that it was very slow for local development and problematic for production deployment. You cannot, but it was our real experience.
Another thing to keep in mind is that the X action requirement can lead to many different events, thus knowing all the events generated by the command / event / that are ever useful. They can also relate to different types of objects, for example, clicking the buy button in the basket can trigger account events and warehousing. A consumer application may want to know all this, so we added CorrelationId. This meant that the consumer could request all the events that arose as a result of their request. You will see this in the diagram .
In particular, using SQL, we found that performance really becomes a bottleneck if indexes and partitions are not used properly. Remember that events must be transmitted in the reverse order if you use snapshots. We tried several different indexes and found that in practice, some additional indexes are needed to debug real-world real-world applications. Again you will see this in the diagram .
Other production metadata was useful during production investigations; timestamps allowed us to understand the order in which events were stored rather than generated. This gave us some help in creating a system in which there were especially many events that caused a huge number of events, providing us with information about the performance of such things as networks and the distribution of systems over a network.