{"id":5947,"date":"2017-07-12T16:43:58","date_gmt":"2017-07-12T23:43:58","guid":{"rendered":"https:\/\/www.backerkit.com\/blog\/?p=5947"},"modified":"2020-02-21T00:40:50","modified_gmt":"2020-02-21T08:40:50","slug":"getting-started-with-event-sourcing-in-postgres","status":"publish","type":"post","link":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/","title":{"rendered":"Getting Started with Event Sourcing in Postgres"},"content":{"rendered":"<span class=\"cb-itemprop\" itemprop=\"reviewBody\"><p>In a previous <a href=\"https:\/\/www.backerkit.com\/blog\/tracking-and-analyzing-emails-using-webhooks\/\">post<\/a> we explored how our application tracks sent emails. We set things up to update or insert a record for each email with the current state (delivered, dropped, etc). In this post we&#8217;ll look at an alternative data model where we&#8217;ll store each event as its own record and then roll up the events to get the current state of a particular email.<\/p>\n<p>With this different modeling we&#8217;ll be able to understand the full lifecycle of an email allowing us to debug issues better as well as have a better understanding of how our users interact with our emails. In our original setup we could only know about the most recent email event. We&#8217;ll use the concept of <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/architecture\/patterns\/event-sourcing\">event sourcing<\/a> to maintain this history.<\/p>\n<h2>Our Setup<\/h2>\n<p>Here&#8217;s what our table will look like:<\/p>\n<pre><code>CREATE TABLE emails (\n  id SERIAL PRIMARY KEY,\n  message_id varchar NOT NULL,\n  status varchar NOT NULL,\n  created_at timestamp DEFAULT current_timestamp,\n  updated_at timestamp DEFAULT current_timestamp\n);\n\nCREATE INDEX ON emails (message_id);\n<\/code><\/pre>\n<p>Here is some data to get us started.<\/p>\n<pre><code>INSERT INTO emails VALUES (\n  DEFAULT, 'first-message', 'sent', now(), now()\n);\nINSERT INTO emails VALUES (\n  DEFAULT, 'first-message', 'delivered', now(), now()\n);\nINSERT INTO emails VALUES (\n  DEFAULT, 'first-message', 'opened', now(), now()\n);\nINSERT INTO emails VALUES (\n  DEFAULT, 'second-message', 'sent', now(), now()\n);\n<\/code><\/pre>\n<p>We have two emails being tracked. One has been sent, delivered, and opened; the other has only been sent, but not yet delivered or opened.<\/p>\n<p>Check out <a href=\"http:\/\/sqlfiddle.com\/#!17\/8a876\/1\">this SQLFiddle<\/a> if you&#8217;d like to explore the data or queries yourself.<\/p>\n<h2>Query for a Single Message<\/h2>\n<p>Given we have a single <code>message_id<\/code> (<code>'first-message'<\/code>, for example) and we want to find its current state, we could write a query like this one:<\/p>\n<pre><code>SELECT\n  message_id,\n  status\nFROM emails\nWHERE message_id = 'first-message'\nORDER BY created_at DESC\nLIMIT 1;\n<\/code><\/pre>\n<p>Our output looks like:<\/p>\n<pre><code>message_id     | status\n-----------------------\nfirst-message  | opened\n<\/code><\/pre>\n<h2>Query Across All Messages<\/h2>\n<p>If we wanted to get the current state of all the emails, we would need to write a different query than we&#8217;re used to. We still want the most recent event but now we want it across a list of emails. As a result we want to get a single event (the most recent one) for each message. We can use Postgres&#8217; <a href=\"http:\/\/www.postgresqltutorial.com\/postgresql-select-distinct\/\"><code>DISTINCT ON<\/code><\/a> notation to do just that.<\/p>\n<pre><code>SELECT DISTINCT ON (message_id)\n  message_id,\n  status\nFROM emails\nORDER BY message_id, created_at DESC;\n<\/code><\/pre>\n<p><strong>Note<\/strong>: this query has been updated from the original post to be simpler as a result of feedback on <a href=\"https:\/\/news.ycombinator.com\/item?id=14758140\">Hacker News<\/a>.<\/p>\n<p>Our output looks like:<\/p>\n<pre><code>message_id     | status\n-----------------------\nfirst-message  | opened\nsecond-message | sent\n<\/code><\/pre>\n<p>With this kind of data model and queries, we can now understand the current state of the world as well as interrogate our system to understand all the statuses through which an email went.<\/p>\n<h2>Further Reading<\/h2>\n<ul>\n<li>Martin Fowler&#8217;s <a href=\"https:\/\/martinfowler.com\/eaaDev\/EventSourcing.html\">event sourcing<\/a> post<\/li>\n<li>Explore <a href=\"https:\/\/robots.thoughtbot.com\/active-record-eager-loading-with-query-objects-and-decorators\">refactoring this query<\/a> into an ORM layer<\/li>\n<li>Learn about using <a href=\"https:\/\/stackoverflow.com\/a\/7630564\/1949363\"><code>EXPLAIN ANALYZE<\/code><\/a> to understand performance of this kind of query<\/li>\n<li>If this stuff is interesting to you and you&#8217;d like to chat, drop us a line: <a href=\"&#x6d;&#97;i&#x6c;&#116;o&#x3a;&#101;&#110;&#x67;&#105;&#110;&#x65;&#101;&#114;&#x69;&#x6e;&#103;&#x40;&#x62;&#97;&#x63;&#x6b;&#101;&#x72;&#x6b;&#105;t&#x2e;&#99;o&#x6d;\">&#101;&#110;&#x67;&#105;&#110;&#x65;&#101;&#114;&#x69;&#x6e;&#103;&#x40;&#x62;&#97;&#x63;&#x6b;&#101;&#x72;&#x6b;&#105;t&#x2e;&#99;o&#x6d;<\/a><\/li>\n<\/ul>\n<\/span>","protected":false},"excerpt":{"rendered":"<p>In a previous post we explored how our application tracks sent emails. We set things up to update or insert a record for each email with the current state (delivered, dropped, etc). In this post we&#8217;ll look at an alternative data model where we&#8217;ll store each event as its own record and then roll up [&hellip;]<\/p>\n","protected":false},"author":20,"featured_media":5953,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[45],"tags":[],"class_list":["post-5947","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering"],"acf":[],"yoast_head":"\n<title>Getting Started with Event Sourcing in Postgres | BackerKit<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:title\" content=\"Getting Started with Event Sourcing in Postgres | BackerKit\" \/>\n<meta name=\"twitter:description\" content=\"In a previous post we explored how our application tracks sent emails. We set things up to update or insert a record for each email with the current state (delivered, dropped, etc). In this post we&#8217;ll look at an alternative data model where we&#8217;ll store each event as its own record and then roll up [&hellip;]\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png\" \/>\n<meta name=\"twitter:creator\" content=\"@simontaranto\" \/>\n<meta name=\"twitter:site\" content=\"@backerkit\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Simon Taranto\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\"},\"author\":{\"name\":\"Simon Taranto\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/860d1b2b8b38a20bce936391e5ed85de\"},\"headline\":\"Getting Started with Event Sourcing in Postgres\",\"datePublished\":\"2017-07-12T23:43:58+00:00\",\"dateModified\":\"2020-02-21T08:40:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\"},\"wordCount\":413,\"publisher\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png\",\"articleSection\":[\"Engineering\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\",\"url\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\",\"name\":\"Getting Started with Event Sourcing in Postgres | BackerKit\",\"isPartOf\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png\",\"datePublished\":\"2017-07-12T23:43:58+00:00\",\"dateModified\":\"2020-02-21T08:40:50+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage\",\"url\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png\",\"contentUrl\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png\",\"width\":1558,\"height\":748},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.backerkit.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Getting Started with Event Sourcing in Postgres\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#website\",\"url\":\"https:\/\/www.backerkit.com\/blog\/\",\"name\":\"Crowdfunding Blog &amp; Resources | BackerKit\",\"description\":\"The BackerKit crowdfunding blog provides expert advice and success stories to help you plan, manage, and deliver a successful crowdfunding campaign.\",\"publisher\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.backerkit.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#organization\",\"name\":\"BackerKit\",\"url\":\"https:\/\/www.backerkit.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/02\/BackerKit-logo.png\",\"contentUrl\":\"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/02\/BackerKit-logo.png\",\"width\":1200,\"height\":345,\"caption\":\"BackerKit\"},\"image\":{\"@id\":\"https:\/\/www.backerkit.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/Backerkit\/\",\"https:\/\/x.com\/backerkit\",\"https:\/\/www.instagram.com\/backerkit\/\",\"https:\/\/www.linkedin.com\/company\/backerkit\",\"https:\/\/www.youtube.com\/channel\/UC3gch2VsESfv0XW36W7BBQg\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/860d1b2b8b38a20bce936391e5ed85de\",\"name\":\"Simon Taranto\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/90212df57bf38f6f4e9cc846a23e49e9b9e19e8a5b8c32d5d01ec4d2fbd8b227?s=96&d=monsterid&r=pg\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/90212df57bf38f6f4e9cc846a23e49e9b9e19e8a5b8c32d5d01ec4d2fbd8b227?s=96&d=monsterid&r=pg\",\"caption\":\"Simon Taranto\"},\"sameAs\":[\"http:\/\/www.backerkit.com\",\"https:\/\/x.com\/simontaranto\"],\"url\":\"https:\/\/www.backerkit.com\/blog\/author\/simon\/\"}]}<\/script>\n","yoast_head_json":{"title":"Getting Started with Event Sourcing in Postgres | BackerKit","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/","twitter_card":"summary_large_image","twitter_title":"Getting Started with Event Sourcing in Postgres | BackerKit","twitter_description":"In a previous post we explored how our application tracks sent emails. We set things up to update or insert a record for each email with the current state (delivered, dropped, etc). In this post we&#8217;ll look at an alternative data model where we&#8217;ll store each event as its own record and then roll up [&hellip;]","twitter_image":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png","twitter_creator":"@simontaranto","twitter_site":"@backerkit","twitter_misc":{"Written by":"Simon Taranto","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#article","isPartOf":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/"},"author":{"name":"Simon Taranto","@id":"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/860d1b2b8b38a20bce936391e5ed85de"},"headline":"Getting Started with Event Sourcing in Postgres","datePublished":"2017-07-12T23:43:58+00:00","dateModified":"2020-02-21T08:40:50+00:00","mainEntityOfPage":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/"},"wordCount":413,"publisher":{"@id":"https:\/\/www.backerkit.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage"},"thumbnailUrl":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png","articleSection":["Engineering"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/","url":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/","name":"Getting Started with Event Sourcing in Postgres | BackerKit","isPartOf":{"@id":"https:\/\/www.backerkit.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage"},"image":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage"},"thumbnailUrl":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png","datePublished":"2017-07-12T23:43:58+00:00","dateModified":"2020-02-21T08:40:50+00:00","breadcrumb":{"@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#primaryimage","url":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png","contentUrl":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/07\/Getting-Started-with-Event-Sourcing-in-Postgres.png","width":1558,"height":748},{"@type":"BreadcrumbList","@id":"https:\/\/www.backerkit.com\/blog\/getting-started-with-event-sourcing-in-postgres\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.backerkit.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Getting Started with Event Sourcing in Postgres"}]},{"@type":"WebSite","@id":"https:\/\/www.backerkit.com\/blog\/#website","url":"https:\/\/www.backerkit.com\/blog\/","name":"Crowdfunding Blog &amp; Resources | BackerKit","description":"The BackerKit crowdfunding blog provides expert advice and success stories to help you plan, manage, and deliver a successful crowdfunding campaign.","publisher":{"@id":"https:\/\/www.backerkit.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.backerkit.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.backerkit.com\/blog\/#organization","name":"BackerKit","url":"https:\/\/www.backerkit.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.backerkit.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/02\/BackerKit-logo.png","contentUrl":"https:\/\/www.backerkit.com\/blog\/wp-content\/uploads\/2017\/02\/BackerKit-logo.png","width":1200,"height":345,"caption":"BackerKit"},"image":{"@id":"https:\/\/www.backerkit.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Backerkit\/","https:\/\/x.com\/backerkit","https:\/\/www.instagram.com\/backerkit\/","https:\/\/www.linkedin.com\/company\/backerkit","https:\/\/www.youtube.com\/channel\/UC3gch2VsESfv0XW36W7BBQg"]},{"@type":"Person","@id":"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/860d1b2b8b38a20bce936391e5ed85de","name":"Simon Taranto","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.backerkit.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/90212df57bf38f6f4e9cc846a23e49e9b9e19e8a5b8c32d5d01ec4d2fbd8b227?s=96&d=monsterid&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/90212df57bf38f6f4e9cc846a23e49e9b9e19e8a5b8c32d5d01ec4d2fbd8b227?s=96&d=monsterid&r=pg","caption":"Simon Taranto"},"sameAs":["http:\/\/www.backerkit.com","https:\/\/x.com\/simontaranto"],"url":"https:\/\/www.backerkit.com\/blog\/author\/simon\/"}]}},"_links":{"self":[{"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/posts\/5947","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/comments?post=5947"}],"version-history":[{"count":0,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/posts\/5947\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/media\/5953"}],"wp:attachment":[{"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/media?parent=5947"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/categories?post=5947"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.backerkit.com\/blog\/wp-json\/wp\/v2\/tags?post=5947"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}