EventStore - Learnings of a broken man
— EventStore, Docker — 4 min read
EventStore and .NET
If you're looking to connect to EventStore from a .NET application, you've got three choices: HTTP, TCP and gRPC. HTTP is available for all langauges that support HTTP requests (so almost all of them). The TCP client is a package only available as a .NET SDK and has now been deprecated by EventStore. Finally, gRPC is the one to choose if you're new. It is the future of EventStore clients and has good support across a range of languages include .NET, Java and Rust(!).
The TCP Client
The gRPC client was only released by EventStore in the last few years. Before that, the best option for .NET developers was the TCP client.
There are various modes of operation for EventStore. You have secure and insecure; single node and cluster. Setting the clients up to work with these different options is fiddly and configuration can be totally different depending on what you're aiming for. In my case, I was attempting to set up a local EventStore instance to work with the current code base without any changes to the code outside of basic socket and credential changes. This ended up requiring a secure cluster setup as no other configuration would have met my requirements (basically, I needed a copy of live), however, for local testing, the recommendation is an insecure single node as this is the least complicated and easiest to set up.
Insecure Single Node Setup
My experience with EventStore's documentation is just plain bad... They're examples don't work, even when followed to the letter and a lot of important information is hidden deep within paragraphs instead of highlighted separately.
There are various ways of running a local EventStore instance, the one I would recommend is spinning up a container using Docker. It's simple and you get repeatable results (unless they remove their Docker image).
Setup with Docker
For the following, you'll need to make sure you have Docker and, by extension, docker-compose installed.
- Create a new empty folder and name it something suitable (i.e. EventStoreContainer)
- Create a new file and name it docker-compose.yaml
- Add the following to the file, in our case, we're going to be running a 20.10.2 instance which is the latest LTS version of EventStore. If you want to use a different version, go to their Docker Hub page and find it's tag for reference there.
1version: '3.4'2
3services:4 eventstore.db:5 image: eventstore/eventstore:20.10.2-buster-slim6 environment:7 - EVENTSTORE_CLUSTER_SIZE=18 - EVENTSTORE_RUN_PROJECTIONS=All9 - EVENTSTORE_START_STANDARD_PROJECTIONS=true10 - EVENTSTORE_EXT_TCP_PORT=111311 - EVENTSTORE_EXT_HTTP_PORT=211312 - EVENTSTORE_INSECURE=true13 - EVENTSTORE_ENABLE_EXTERNAL_TCP=true14 - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true15 ports:16 - "1113:1113"17 - "2113:2113"18 volumes:19 - type: volume20 source: eventstore-volume-data21 target: /var/lib/eventstore22 - type: volume23 source: eventstore-volume-logs24 target: /var/log/eventstore25
26volumes:27 eventstore-volume-data:28 eventstore-volume-logs:
Open a command prompt in that directory and run
docker-compose up
This will start an instance of EventStore and you should see an output in the terminal window. If you have any issues connecting, this is the place to look.Go to localhost:2113 and you should now have access to the EventStore dashboard.
TCP Client Configuration
- Open a new CLI project in .NET in your editor of choice.
- Go to the package manager and include a reference to the EventStore.Client package from NuGet. There will be a few other client options but these are the gRPC clients.
- The following code is going to setup a connection with TLS disabled from the client end and append an event to test-stream.
1var settings = ConnectionSettings.Create()2 .DisableTls();3
4var conn = EventStoreConnection.Create("ConnectTo=tcp://localhost:1113", settings);5
6await conn.ConnectAsync();7
8var data = Encoding.UTF8.GetBytes("{\"a\":\"2\"}");9var metadata = Encoding.UTF8.GetBytes("{}");10var evt = new EventData(Guid.NewGuid(), "testEvent", true, data, metadata);11
12await conn.AppendToStreamAsync("test-stream", ExpectedVersion.Any, evt);
Before we move on, there are a few things of note here. The first is that there are multiple different ways to setup a connection to EventStore from the client that all achieve the same behaviour. This, in my opinion, is hugely confusing and unecessary. Below, I'll list some interchangable variations of line 4 that will all do the same thing.
1var conn = EventStoreConnection.Create(settings.Build(), new Uri("tcp://localhost:1113"))
1var conn = EventStoreConnection.Create("ConnectTo=tcp://localhost:1113;UseSslConnection=false");
Anyway, moving on
- When you've run the code, switch back to your localhost:2113 tab and go to the Stream Browser tab (fyi, this would not have been available if we hadn't include the
EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true
config option in the yaml).
In that tab, you should see 1 recently created Stream called test-stream with one event inside it. And that's it, you've connected and successfully appended an event.
Secure Cluster Setup
Setting up EventStore as a secure cluster of nodes is what you would expect from a production environment. Ordinarily, the nodes would be on different server instances to achieve load-balancing and high-availability. In our case however, the nodes will be hosted in separate Docker instances on your local machine.
Setup with Docker
- Create a new empty folder and name it something suitable (i.e. EventStoreContainer)
- Create a new file and name it docker-compose.yaml
- Add the following to the file, in our case, we're going to be running a 20.10.2 instance which is the latest LTS version of EventStore. If you want to use a different version, go to their Docker Hub page and find it's tag for reference there.
1version: "3.5"2
3services:4 setup:5 image: eventstore/es-gencert-cli:1.0.26 entrypoint: bash7 user: "1000:1000"8 command: >9 -c "mkdir -p ./certs && cd /certs10 && es-gencert-cli create-ca11 && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.30.240.11 -dns-names localhost12 && es-gencert-cli create-node -out ./node2 -ip-addresses 127.0.0.1,172.30.240.12 -dns-names localhost13 && es-gencert-cli create-node -out ./node3 -ip-addresses 127.0.0.1,172.30.240.13 -dns-names localhost14 && find . -type f -print0 | xargs -0 chmod 666"15 container_name: setup16 volumes:17 - ./certs:/certs18
19 node1.eventstore: &template20 image: eventstore/eventstore:20.10.2-buster-slim21 container_name: node1.eventstore22 env_file:23 - vars.env24 environment:25 - EVENTSTORE_INT_IP=172.30.240.1126 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=211127 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=111128 - EVENTSTORE_GOSSIP_SEED=172.30.240.12:2113,172.30.240.13:211329 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca30 - EVENTSTORE_CERTIFICATE_FILE=/certs/node1/node.crt31 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node1/node.key32 healthcheck:33 test:34 [35 "CMD-SHELL",36 "curl --fail --insecure https://node1.eventstore:2113/health/live || exit 1",37 ]38 interval: 5s39 timeout: 5s40 retries: 2441 ports:42 - 1111:111343 - 2111:211344 volumes:45 - ./certs:/certs46 depends_on:47 - setup48 restart: always49 networks:50 clusternetwork:51 ipv4_address: 172.30.240.1152
53 node2.eventstore:54 <<: *template55 container_name: node2.eventstore56 env_file:57 - vars.env58 environment:59 - EVENTSTORE_INT_IP=172.30.240.1260 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=211261 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=111262 - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.13:211363 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca64 - EVENTSTORE_CERTIFICATE_FILE=/certs/node2/node.crt65 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node2/node.key66 healthcheck:67 test:68 [69 "CMD-SHELL",70 "curl --fail --insecure https://node2.eventstore:2113/health/live || exit 1",71 ]72 interval: 5s73 timeout: 5s74 retries: 2475 ports:76 - 1112:111377 - 2112:211378 networks:79 clusternetwork:80 ipv4_address: 172.30.240.1281
82 node3.eventstore:83 <<: *template84 container_name: node3.eventstore85 environment:86 - EVENTSTORE_INT_IP=172.30.240.1387 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=211388 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=111389 - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:211390 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca91 - EVENTSTORE_CERTIFICATE_FILE=/certs/node3/node.crt92 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node3/node.key93 healthcheck:94 test:95 [96 "CMD-SHELL",97 "curl --fail --insecure https://node3.eventstore:2113/health/live || exit 1",98 ]99 interval: 5s100 timeout: 5s101 retries: 24102 ports:103 - 1113:1113104 - 2113:2113105 networks:106 clusternetwork:107 ipv4_address: 172.30.240.13108
109networks:110 clusternetwork:111 name: eventstoredb.local112 driver: bridge113 ipam:114 driver: default115 config:116 - subnet: 172.30.240.0/24
Add a sub-directory to your EventStoreContainer folder called certs
Open a command prompt in that directory and run
docker-compose up
This will start an instance of EventStore and you should see an output in the terminal window. If you have any issues connecting, this is the place to look.After exectuing the above, the es-gencert-cli will generate a root ca and an individual certificate for each node. You don't need much knowledge of certificates here, just an understanding that without them, TLS won't work. Therefore, we're going to need to install them for you current user, even if it's only temporarily.
Open the certs directory and install the ca certificate
Double click the certificate and select Install Certificate...
Select Current User and click Next
Select Place all certificates in the following store and click Browse...
From the list select Trusted Root Certification Authorities and click OK
Then click Next and then Finish
A security warning will pop up, click Yes and the EventStoreDB CA will now be installedNow, go to https://localhost:2113 and you should have secure access with TLS.
In your command prompt, execute
certmgr
This will open up your the certificate manager for your current user. This will allow you to import and remove certificates.