{"id":13566085,"url":"https://github.com/isalevine/event-sourcing-user-app","last_synced_at":"2025-04-03T23:31:03.085Z","repository":{"id":40130587,"uuid":"260601105","full_name":"isalevine/event-sourcing-user-app","owner":"isalevine","description":"dev.to demo code for Event Sourcing system built around a User model","archived":false,"fork":false,"pushed_at":"2023-01-19T18:41:23.000Z","size":1152,"stargazers_count":9,"open_issues_count":33,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-04T20:41:49.070Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/isalevine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-02T02:45:44.000Z","updated_at":"2024-03-22T13:09:07.000Z","dependencies_parsed_at":"2023-02-05T08:45:57.709Z","dependency_job_id":null,"html_url":"https://github.com/isalevine/event-sourcing-user-app","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isalevine%2Fevent-sourcing-user-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isalevine%2Fevent-sourcing-user-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isalevine%2Fevent-sourcing-user-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isalevine%2Fevent-sourcing-user-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/isalevine","download_url":"https://codeload.github.com/isalevine/event-sourcing-user-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247097667,"owners_count":20883122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T13:02:01.714Z","updated_at":"2025-04-03T23:31:02.002Z","avatar_url":"https://github.com/isalevine.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# This README is the tutorial to create an Event Sourcing system in Ruby on Rails.\nThe article can be found on Dev.to here:\n[https://dev.to/isalevine/building-an-event-sourcing-system-in-rails-part-2-building-our-event-pattern-from-scratch-168p-temp-slug-1608448?preview=2640d0e9af2c0b95d6c79592e33429c8604aa7b6b44734ca55e33ca5c19942669fe6b9913cc71b333787bee60dca1f7993d03a9a2e78455a554b3d3c](https://dev.to/isalevine/building-an-event-sourcing-system-in-rails-part-2-building-our-event-pattern-from-scratch-168p-temp-slug-1608448?preview=2640d0e9af2c0b95d6c79592e33429c8604aa7b6b44734ca55e33ca5c19942669fe6b9913cc71b333787bee60dca1f7993d03a9a2e78455a554b3d3c)\n\n# To Recap: What is Event Sourcing?\n\nEvent Sourcing is a system design pattern that emphasizes recording changes to data via immutable events.\n\nIn other words: *every time your data changes*, you save an event to your database with the details. \n\n**Those events never change or go away.** That way, you have a permanent, unchanging history of how your data reached its current state!\n\n\n# What this article covers\nWe will primarily be working off of [Kickstarter's event sourcing example.](https://kickstarter.engineering/event-sourcing-made-simple-4a2625113224)\n\nTo create our Event pattern, we’ll take the following steps:\n-   Get our Rails app up and running\n\t-  \t`User` model and controller\n\t-   PostgreSQL for our database\n-   Set up our environment to test our Events\n\t-   Postico to inspect our database\n\t-   Insomnia for REST client\n-   Add our Events pattern\n\t-   What is an Event, and what Event data will we store in the database?\n\t-   The BaseEvent that other Event classes will inherit from\n\t-   `Events::User::Created`\n\t-   `Events::User::Destroyed`\n\n\n# Getting our Rails app up and running\n\n## Let’s go ahead and create our new Rails app\nWe’ll set the database to PostgreSQL with `--database=postgresql` and skip tests with `--skip-test`, as we will be adding RSpec manually later.\n```ruby\nrails new event-sourcing-user-app --database=postgresql --skip-test\n```\n\n## Let’s add our `User` model\nOur `User` model will have several fields:\n-   `name` String,\n-   `email` String,\n-   `password_digest` String (for bcrypt)\n-   `deleted` Boolean (remember, part of event sourcing is that we **never delete data**—instead, we will flag certain Users as _being_ deleted, and scope our queries appropriately)\n\t-   this field also needs to be `null: false`, and be set to `default: false`\n\nWe’ll start this with Rails one-liner:\n```ruby\nrails g model User name email password_digest deleted:boolean\n```\n\nAnd inside the new migration, tweak the `t.boolean :deleted` to be `null: false` and `default: false`:\n```ruby\n# db/migrate/20200502025357_create_users.rb\n\nclass CreateUsers \u003c ActiveRecord::Migration[6.0]\n  def change\n    create_table :users do |t|\n      t.string :name\n      t.string :email\n      t.string :password_digest\n      t.boolean :deleted, null: false, default: false\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## Add a `User` controller and routes\nOur User controller needs to have two actions, a `create` and `destroy` action, to handle the Events we want to make.\n\nLet’s create our controller manually, since we don’t need any views to be generated. In `app/controllers`, create a `users_controller` and add `def create` and `def destroy` actions:\n```ruby\n# app/controllers/users_controller.rb\n\nclass UsersController \u003c ApplicationController\n  def create\n  end\n\n  def destroy\n  end\nend\n```\n\nSince we are not implementing auth yet, we’ll also add a `skip_before_action` hook to make testing our code easier:\n```ruby\n# app/controllers/users_controller.rb\n\nclass UsersController \u003c ApplicationController\n  skip_before_action :verify_authenticity_token, only: [:create, :destroy]\n\n  def create\n  end\n\n  def destroy\n  end\nend\n```\n\nNext, let’s manually add a POST and DELETE route that will go to the `create` and `destroy` actions in our controller:\n```ruby\n# config/routes.rb\n\nRails.application.routes.draw do\n  post 'users/create', to: 'users#create'\n  delete 'users/destroy', to: 'users#destroy'\nend\n```\n\nRun `rails routes` in your console to see that the routes are set up correctly:\n```\n[13:29:44] (master) event-sourcing-user-app\n// ♥ rails routes                         \nPrefix           Verb     URI Pattern                 Controller#Action\nusers_create     POST     /users/create(.:format)     users#create\nusers_destroy    DELETE   /users/destroy(.:format)    users#destroy\n```\n\n## Run database migrations\nNow, let’s create our databases and run our migrations in the usual two-step:\n```ruby\nrails db:create\nrails db:migrate\n```\n\n\n# Setting up our environment to test our Events\n\n## Set up Postico to view our PostgreSQL database\nIf you’re not familiar with [Postico](https://eggerapps.at/postico/), it’s a a database management tool and viewer with a great free trial. \n\nDownload and install from their website, and open it up. Go ahead and hit `Connect` to in the `localhost` using its default settings:\n\n![Postico's main page](https://dev-to-uploads.s3.amazonaws.com/i/3e01npyfqoi9iwb84i2t.png)\n\nFrom here, click the `localhost` button at the top to go to a list of available databases:\n\n![Postico landing page inside localhost](https://dev-to-uploads.s3.amazonaws.com/i/ckp52em08tqocdr1yno6.png)\n\nAnd now, we should be able to select our development database:\n\n![Postico page listing available databases](https://dev-to-uploads.s3.amazonaws.com/i/t7cm9r4lp4el3va6mms3.png)\n\nSelect our `users` table:\n\n![Postico page inside development database, showing users table](https://dev-to-uploads.s3.amazonaws.com/i/sj9trljc4a6wubgx7fcj.png)\n\nAnd, hurray—there’s our User model, with it’s four fields:\n\n![Postico users table](https://dev-to-uploads.s3.amazonaws.com/i/ge2z2vqt53m56cgxbvi0.png)\n\n\n## Set up Insomnia to send HTTP requests\nLikewise, if you’re not familiar with [Insomnia](https://insomnia.rest/), it’s a tool for sending HTTP requests to test RESTful APIs. We’ll be using **Insomnia Core**.\n\nDownload, install, and open it up:\n\n![Insomnia Core main page](https://dev-to-uploads.s3.amazonaws.com/i/7fku2239n0kymgfgl0lv.png)\n\nCreate a folder for our project, `event-sourcing-user-app`:\n\n![Insomnia showing new project folder](https://dev-to-uploads.s3.amazonaws.com/i/388jrxxujj7n5e52fs06.png)\n\nLet’s create our first request. We’ll make it a POST request, which we’ll user for a **create User** route:\n\n![Insomnia showing new request being set to POST](https://dev-to-uploads.s3.amazonaws.com/i/khu76n08h5ubparkc8yk.png)\n\nAnd lastly, we’ll set the target URL to `localhost:3000/users/create` for testing later:\n\n![Insomnia showing target URL for Create User request](https://dev-to-uploads.s3.amazonaws.com/i/uu41v34shfies4g4dnju.png)\n\nYay, now Insomnia’s ready to go! We’ll just need to fill out the body of our request with a hash once we have our Events created.\n\n## Testing the `create` action with `byebug` and Insomnia\n\nYou can test out the routes by adding a `byebug` to the controller action:\n```ruby\n# app/controllers/users_controller.rb\n\ndef create\n  byebug\nend\n```\n\nFire up `rails s`, and send a POST request to `localhost:3000/users/create` in Insomnia. In your console, you will see `byebug` session:\n\n![screenshot showing Insomnia request, and console inside a byebug session](https://dev-to-uploads.s3.amazonaws.com/i/6aywcx4o8j104jdyshq1.png)\n\nGreat, we can see our route working as expected!\n\n**Now, we’re ready to build our event pattern!**\n\n\n\n# What is an Event?\nIn our event sourcing system, each Event will be **a Rails model that stores information about changes to our data**. \n\nOur goal is to build two events:\n-   `Events::User::Created` — this will record:\n\t-   `payload`: a hash containing the `name`, `email`, and `password` params to create the User\n\t-   `user_id`: the created User’s `id`, used as a `belongs_to` relationship\n\t-   `event_type`: a String to show that this `user_event` is the `”Created”` type\n\t-   timestamps\n-   `Events::User::Destroyed` — this will record:\n\t-   `payload`: a hash containing the `id` for the User to be flagged as deleted\n\t-   `user_id`: the target User’s `id`, used as a `belongs_to` relationship\n\t-   `event_type`: a String to show that this `user_event` is the `”Destroyed”` type\n\t-   timestamps\n\nWhen our Rails app creates or destroys a User, this will also trigger creating a new Event.\n\nThese events will be saved to our database, and will be **immutable** to serve as a permanent log of changes.\n\nSince we might end up having _a lot_ of User-related events, we’re also including the `event_type` field on our User events so we can store them all in one `user_events` table—and easily add add more later!\n\n\n# The `Events::BaseEvent`\nOur events will be built through inheritance. At the top of the chain, we will define `Events::BaseEvent` where a lot of the event functionality will live.\n\nSince all of our events will be Rails models, go ahead and create a new `/events` directory inside `app/models`.\n\nNow, we can create our BaseEvent:\n```ruby\n# app/models/events/base_event.rb\n\nclass Events::BaseEvent \u003c ActiveRecord::Base\nend\n```\n\n## `abstract_class`\nSince the BaseEvent only exists for inheritance, we can make it an `abstract_class` so Rails knows not to try to load any records for it:\n```ruby\n# app/models/events/base_event.rb\n\nclass Events::BaseEvent \u003c ActiveRecord::Base\n  self.abstract_class = true\nend\n```\n\n## `apply(aggregate)` and `apply_and_persist`\nEach event will have to define its own `apply` method. This method will accept an `aggregate`—another model, in our case a User—and update its attributes.\n_(The term `aggregate` comes from the Kickstarter event sourcing system, and [you can read more about it here](https://kickstarter.engineering/event-sourcing-made-simple-4a2625113224). Basically, `aggregates` are models that receive changes via `events`.)_\n\nOn BaseEvent, we’ll simply raise a `NotImplementedError`. This will enforce us having to define it explicitly on each event, thus overriding the error via inheritance.\n\nThe BaseEvent will also have a `before_create` hook that calls `apply_and_persist`. This will call `apply`, then `save!` the update to the database. \n_(It will also set the event’s `aggregate_id`, specifically for Created events where the `id` doesn’t exist until after `save!` is called.)_\n\nLet’s look at the code we’ll add:\n\n```ruby\n# app/models/events/base_event.rb\n\nbefore_create :apply_and_persist\n\ndef apply(aggregate)\n  raise NotImplementedError\nend\n\nprivate def apply_and_persist\n  # Lock the database row! (OK because we're in an ActiveRecord callback chain transaction)\n  aggregate.lock! if aggregate.persisted?\n\n  # Apply!\n  self.aggregate = apply(aggregate)\n\n  #Persist!\n  aggregate.save!\n\n  # Update aggregate_id with id from newly created User\n  self.aggregate_id = aggregate.id if aggregate_id.nil?\nend\n```\n\n\n\n## `after_initialize` and `event_type`\nNo matter what kind of event we instantiate, there are a couple attributes we want to set right away:\n-   `event_type` — every Event needs to be explicitly categorized for when it’s stored in the `user_events` table as a `BaseEvent` record\n-   `payload` — since we always expect `payload` to be accessible as a hash (and stored in our PostgreSQL database as JSON), we’ll add a `||` clause to set it to `{}` if the event accepts no params\n\nSo, we’ll add an `after_initialize` hook to set those attributes:\n```ruby\n# app/models/events/base_event.rb\n\nafter_initialize do\n  self.event_type = event_type\n  self.payload ||= {}\nend\n\ndef event_type\n  self.attributes[\"event_type\"] || self.class.to_s.split(\"::\").last\nend\n```\n\nAbove, we define `event_type` to quickly access its own type via `attributes` if loaded from our database—or upon first creation, deducing its type from the Event class’s name.\n\n\n## `self.payload_attributes(*attributes)`\nIn each Event class we create, we want the option to define possible `payload_attributes` we want to record.\n\nOn BaseEvent, `self.payload_attributes` will create the getters and setters for our payload fields:\n```ruby\n# app/models/events/base_event.rb\n\ndef self.payload_attributes(*attributes)\n  @payload_attributes ||= []\n\n  attributes.map(\u0026:to_s).each do |attribute|\n    @payload_attributes \u003c\u003c attribute unless @payload_attributes.include?(attribute)\n\n    define_method attribute do\n      self.payload ||= {}\n      self.payload[attribute]\n    end\n\n    define_method \"#{attribute}=\" do |argument|\n      self.payload ||= {}\n      self.payload[attribute] = argument\n    end\n  end\n\n  @payload_attributes\nend\n```\n\nUltimately, this will let us define attributes like this at the top of each new Event class: `payload_attributes :name, :email, :password`\n\n\n## `find_or_build_aggregate`\nWe want our events to be aware of their aggregates—in our case, the target User—and be able to either look it up, or create a new one.\n\nWe’ll add a `before_validation` hook (which gets called _really early_ in the `.create` lifecycle) which will either look up or create the aggregate, based on whether an `id` is present in the event’s params:\n\n```ruby\n# app/models/events/base_event.rb\n\nbefore_validation :find_or_build_aggregate\n\nprivate def find_or_build_aggregate\n  self.aggregate = find_aggregate if aggregate_id.present?\n  self.aggregate = build_aggregate if self.aggregate.nil?\nend\n\ndef find_aggregate\n  klass = aggregate_name.to_s.classify.constantize\n  klass.find(aggregate_id)\nend\n\ndef build_aggregate\n  public_send \"build_#{aggregate_name}\"\nend\n```\n\nLet’s see the code:\n\n## `aggregate` setters, getters, and get-its-namers\nTo round out our events’ functionality, we’ll want some setters and getters—as well as methods to easily return its type or class name:\n-   `aggregate=(model)` and `aggregate` will set and get the User our event targets\n-   `aggregate_id=(id)` and `aggregate_id` will map to the `user_id` field on our `user_events` table\n-   `self.aggregate_name` gives the Event class awareness of its `belongs_to` relationship’s target class (`#=\u003e User`)\n-   `delegate :aggregate_name, to: :class` will return a Symbol of the aggregate’s class name (`#=\u003e :user`)\n-   `def event_klass` will convert our Event class’s `::BaseEvent` namespace into its appropriate event type (`#=\u003e Events::User::Created`)\n\n```ruby\n# app/models/events/base_event.rb\n\ndef aggregate=(model)\n  public_send \"#{aggregate_name}=\", model\nend\n\n# Return the aggregate record that the event will apply to\ndef aggregate\n  public_send aggregate_name\nend\n\ndef aggregate_id=(id)\n  public_send \"#{aggregate_name}_id=\", id\nend\n\ndef aggregate_id\n  public_send \"#{aggregate_name}_id\"\nend\n\ndef self.aggregate_name\n  inferred_aggregate = reflect_on_all_associations(:belongs_to).first\n  raise \"Events must belong to an aggregate\" if inferred_aggregate.nil?\n  inferred_aggregate.name\nend\n\ndelegate :aggregate_name, to: :class\n\ndef event_klass\n  klass = self.class.to_s.split(\"::\")\n  klass[-1] = event_type\n  klass.join('::').constantize\nend\n```\n\n## Okay, let’s see the whole `Events::BaseEvent`!\n```ruby\n# app/models/events/base_event.rb\n\n# Kickstarter code reference:\n# https://github.com/pcreux/event-sourcing-rails-todo-app-demo/blob/master/app/models/lib/base_event.rb\n\nclass Events::BaseEvent \u003c ActiveRecord::Base\n  before_validation :find_or_build_aggregate\n  before_create :apply_and_persist\n\n  self.abstract_class = true\n\n  def apply(aggregate)\n    raise NotImplementedError\n  end\n\n  after_initialize do\n    self.event_type = event_type\n    self.payload ||= {}\n  end\n\n  def self.payload_attributes(*attributes)\n    @payload_attributes ||= []\n\n    attributes.map(\u0026:to_s).each do |attribute|\n      @payload_attributes \u003c\u003c attribute unless @payload_attributes.include?(attribute)\n\n      define_method attribute do\n        self.payload ||= {}\n        self.payload[attribute]\n      end\n\n      define_method \"#{attribute}=\" do |argument|\n        self.payload ||= {}\n        self.payload[attribute] = argument\n      end\n    end\n\n    @payload_attributes\n  end\n\n  private def find_or_build_aggregate\n    self.aggregate = find_aggregate if aggregate_id.present?\n    self.aggregate = build_aggregate if self.aggregate.nil?\n  end\n\n  def find_aggregate\n    klass = aggregate_name.to_s.classify.constantize\n    klass.find(aggregate_id)\n  end\n\n  def build_aggregate\n    public_send \"build_#{aggregate_name}\"\n  end\n\n  private def apply_and_persist\n    # Lock the database row! (OK because we're in an ActiveRecord callback chain transaction)\n    aggregate.lock! if aggregate.persisted?\n\n    # Apply!\n    self.aggregate = apply(aggregate)\n\n    #Persist!\n    aggregate.save!\n    self.aggregate_id = aggregate.id if aggregate_id.nil?\n  end\n\n  def aggregate=(model)\n    public_send \"#{aggregate_name}=\", model\n  end\n\n  def aggregate\n    public_send aggregate_name\n  end\n\n  def aggregate_id=(id)\n    public_send \"#{aggregate_name}_id=\", id\n  end\n\n  def aggregate_id\n    public_send \"#{aggregate_name}_id\"\n  end\n\n  def self.aggregate_name\n    inferred_aggregate = reflect_on_all_associations(:belongs_to).first\n    raise \"Events must belong to an aggregate\" if inferred_aggregate.nil?\n    inferred_aggregate.name\n  end\n\n  delegate :aggregate_name, to: :class\n\n  def event_type\n    self.attributes[\"event_type\"] || self.class.to_s.split(\"::\").last\n  end\n\n  def event_klass\n    klass = self.class.to_s.split(\"::\")\n    klass[-1] = event_type\n    klass.join('::').constantize\n  end\n\nend\n```\n\n\n# The `user_events` table, and the `Events::User::BaseEvent`\nWe previously mentioned that we will be storing multiple types of User-related events in a single `user_events` table. \n\nTo accomplish this and allow us to easily add more events later, we will create an `Events::User::BaseEvent` which will tell all events in the `Events::User::` namespace to save to the `user_events` table. We will also define a `belongs_to` relationship with a User here.\n\n## `user_events` table\nLet’s go ahead and create our `user_events` table in our database.\n\n[Kickstarter’s event sourcing example](https://kickstarter.engineering/event-sourcing-made-simple-4a2625113224) describes that each `aggregate` (User) has an event table (`user_events`). These event tables will have a similar schema—we will tweak them slightly to match our verbiage:\n\u003e Each Aggregate (ex: subscriptions) has an Event table associated to it (ex: subscription_events).\n\u003e …\n\u003e All events related to an aggregate are stored in the same table. All events tables have a similar schema:\n\u003e `id, aggregate_id, type, data (json), metadata (json), created_at`\n\nA few things we’ll tweak for our code:\n-   `aggregate_id` will be replaced by `user_id`\n-   `type` will be replaced by `event_type` (just to be more explicit)\n-   `data` will be replaced by `payload`, and will still be type JSON\n-   `metadata` will not be included at this time, since our events are relatively simple\n-   `created_at` will not be included, since we will simply rely on ActiveRecord’s default timestamps\n\nWe will create our `user_events` table with a Rails migration:\n```ruby\nrails g migration CreateUserEvents\n```\n\nThis will create our migration with a `create_table` block set up for us:\n```ruby\n# db/migrate/20200502192018_create_user_events.rb\n\nclass CreateUserEvents \u003c ActiveRecord::Migration[6.0]\n  def change\n    create_table :user_events do |t|\n    end\n  end\nend\n```\n\nWe want to add four fields:\n-   a `belongs_to` relationship to a `:user`\n-   an `event_type` String\n-   a `payload` JSON\n-   timestamps\n\n```ruby\n# db/migrate/20200502192018_create_user_events.rb\n\nclass CreateUserEvents \u003c ActiveRecord::Migration[6.0]\n  def change\n    create_table :user_events do |t|\n      t.belongs_to :user, null: false, foreign_key: true\n      t.string :event_type\n      t.json :payload\n\n      t.timestamps\n    end\n  end\nend\n```\n\nRun the migration:\n```ruby\nrails db:migrate\n```\n\nAnd open up Postico to check out the new `user_events` table:\n\n![Postico page showing the user_events table selected](https://dev-to-uploads.s3.amazonaws.com/i/q90r3lfnvvnir8dgh1sa.png)\n\nOur table and fields are ready to go!\n\n![Postico page showing user_events table fields](https://dev-to-uploads.s3.amazonaws.com/i/wrmi6ouzhth65g4mte3p.png)\n\n\n## `Events::User::BaseEvent`\nInside our `app/models/events` directory, create a new `user` directory.\n\nInside that directory, create a new file `base_event.rb`. This gives us the namespacing to create this class:\n```ruby\n# app/models/events/user/base_event.rb\n\nclass Events::User::BaseEvent \u003c Events::BaseEvent\n  self.table_name = \"user_events\"\nend\n```\n\nWith `self.table_name = “user_events”`, any new Event class we create that inherits from `Events::User::BaseEvent` will automatically be saved and retrieved from the `user_events` table!\n\n\n## `belongs_to :user` and `has_many :events`\nSince all our User-related events target a User, it makes sense to create a `has_many / belongs_to` relationship between Users and Events in the `Events::User::` namespace.\n\nSince we’re deep in a namespace that uses the name `User`, to tell Rails to look for the regular top-level `User` model, we need to add `::` before our classnames. This tells our `has_many` and `belongs_to` relationships to look outside the current namespace.\n\nLet’s update our `Events::User::BaseEvent` and `User` classes with the relationships:\n```ruby\n# app/models/events/user/base_event.rb\n\nclass Events::User::BaseEvent \u003c Events::BaseEvent\n  self.table_name = \"user_events\"\n\n  belongs_to :user, class_name: \"::User\"\nend\n\n\n# app/models/user.rb\n\nclass User \u003c ApplicationRecord\n  has_many :events, class_name: \"Events::User::BaseEvent\" \nend\n```\n\nGreat! Now, when we load a User into a `user` variable, we can call `user.events` to load all related events from the `user_events` table.\n\n**We’re now ready to create some real, _usable_ Events!**\n\n\n# Creating a new User with `Events::User::Created`\nWith our BaseEvent pattern in place, we can now build our first event!\n\n`Events::User::Created` will record the params used to create a User, as well as the new User’s id, and the event’s timestamp.\n\n## Build the `Events::User::Created` class\nIn `app/models/events/user`, make a new `created.rb` file. Our class will inherit from `Events::User::BaseEvent` in the same directory:\n```ruby\n# app/models/events/user/created.rb\n\nclass Events::User::Created \u003c Events::User::BaseEvent\nend\n```\n\nAs we defined in the top-level `Events::BaseEvent`, we must define an `apply` method that will take a User instance as its `aggregate` argument:\n```ruby\n# app/models/events/user/created.rb\n\nclass Events::User::Created \u003c Events::User::BaseEvent\n  def apply(user)\n  end\nend\n```\n\nSince we know creating a User requires params with a `name`, `email`, and `password`, we can also add them as a list of symbols to `payload_attributes` to create our getters and setters:\n```ruby\n# app/models/events/user/created.rb\n\nclass Events::User::Created \u003c Events::User::BaseEvent\n  payload_attributes :name, :email, :password\n\n  def apply(user)\n  end\nend\n```\n\n## Add logic to the `apply` method\nThe logic in the event’s `apply` method is where the event’s power lies. It:\n-   takes in a User instance\n-   applies the changes to the User instance, supplied by `payload_attributes`\n-   returns the mutated User instance =\u003e **this is where the top-level BaseEvent receives back the User instance, and calls `save!` to persist the changes in the database!**\n\nThanks to the list of attributes passed to `payload_attributes`, we can simply call the attributes inside our `apply` method to update the User instance:\n```ruby\n# app/models/events/user/created.rb\n\npayload_attributes :name, :email, :password\n\ndef apply(user)\n  user.name = name\n  user.email = email\n  user.password_digest = password\n\n  user\nend\n```\n\nPerfect! Now, all we need to do is tell Insomnia to pass params that contain `name`, `email`, and `password` Strings, and our event will map them to the User model’s `name`, `email`, and `password_digest` fields. \n_(Remember: `password_digest` is related to `bcrypt` functionality, which we will explore in another article.)_\n\n\n## Update our controller to create an Event and use strong params\nBack in our `users_controller`, we need to update two things:\n-   the `create` action needs to call `Events::User::Created.create(payload: user_params)`\n-   add strong params to protect the `user_params` we will pass to `.create(payload: user_params)`\n\nFor the strong params, we will require the `user_params` to have `name`, `email`, and `password` nested inside a `user` key:\n```ruby\n# app/controllers/users_controller.rb\n\nprivate def user_params\n  params.require(:user).permit(:name, :email, :password)\nend\n```\n\nNow, we can safely pass `user_params` to `Events::User::Created.create(payload: user_params)` in the `create` action:\n```ruby\n# app/controllers/users_controller.rb\n\ndef create\n  Events::User::Created.create(payload: user_params)\nend\n\nprivate def user_params\n  params.require(:user).permit(:name, :email, :password)\nend\n```\n\n## Let’s test our event with Insomnia and Postico!\nIf we send the correct params via a POST request to `localhost:3000/users/create`, we expect several behaviors:\n-   a new record in the `user_events` table, with:\n\t-   `event_type “Created”`\n\t-   `payload` with the `user_params`\n\t\t-   note that the `password` will be stored as plaintext =\u003e **this is UNSAFE BEHAVIOR, and is because we have not implemented bcrypt encryption yet!**\n\t-   `user_id` with the newly-created User’s `id`\n-   a new record in the `user` table, with:\n\t-   correct `name`\n\t-   correct `email`\n\t-   `password_digest` that is the plaintext `password` =\u003e **this is UNSAFE BEHAVIOR, and is because we have not implemented bcrypt encryption yet!**\n\nLet’s test it out! \n\nFire up `rails s`, and open up Insomnia. \n\nIn our `Create User` request, set the Body to JSON:\n\n![Insomnia page showing Body type being set to JSON](https://dev-to-uploads.s3.amazonaws.com/i/g3rgthzj73uz2dvhn37w.png)\n\nThen, create a JSON hash with a `”user”` key, which points to a hash containing a `”name”`, `”email”`, and `”password”`:\n\n![Insomnia page showing JSON body with user params](https://dev-to-uploads.s3.amazonaws.com/i/dcq61uzaow04u1m6bc7j.png)\n\nNow hit `Send`, and let’s check out our database tables!\n\nFirst, let’s see if we have an event in our `user_events` table:\n\n![Postico table with first Created event record, overlaid on Insomnia request body](https://dev-to-uploads.s3.amazonaws.com/i/acge8hrnkrq3vm4zsper.png)\n\nSo far, so good!\n_(Remember: **storing passwords as plaintext is UNSAFE BEHAVIOR, and is because we have not implemented bcrypt encryption yet!**)_\n\nNow, let’s check out the `users` table:\n\n![Postico table with first User record, overlaid on Insomnia request body](https://dev-to-uploads.s3.amazonaws.com/i/d82ai0lxwbhoi50sdvhh.png)\n\nTerrific! We now have our new User, `ongo_gablogian`, and a record of the Event and params that created him!\n\n![gif of Danny DeVito as Ongo Gablogian, a parody of Andy Warhol, on Always Sunny](https://dev-to-uploads.s3.amazonaws.com/i/cl772dunyxs83v8cjb24.gif)\n\n**There you have it! Our event sourcing system is now capturing changes to our data!**\n\nAs long as we never alter the data in the `user_events` table, we have a reliable log of how our data got to its current state!\n\n![screenshot of a banner stating MISSION ACCOMPLISHED on Arrested Development](https://dev-to-uploads.s3.amazonaws.com/i/ls69lrx60hiimpan2qpv.jpeg)\n\n\n# Destroying a User with `Events::User::Destroyed`\nNow that we have our pattern in place, it’s very straightforward to create a new Event and record it to our `user_events` table!\n\nSince we **never want to destroy our data**, we implemented a boolean `deleted` field on the User model. When a new User is created, it defaults to `false`.\n\nLet’s create a new event, `Events::User::Destroyed`, that will set the `deleted` field to `true`!\n\n## Create an `app/models/events/user/destroy.rb` file\nIn the same directory as our `Events::User::Created` class, create an equivalent `Events::User::Destroyed` class:\n```ruby\n# app/models/events/user/destroy.rb\n\nclass Events::User::Destroyed \u003c Events::User::BaseEvent\n  def apply(user)\n    user\n  end\nend\n```\nAbove, we start with an `apply` method that simply returns the passed-in User instance.\n\nTo delete a User, we’ll simply require an `id`. Let’s add the `payload_attributes` for it:\n```ruby\n# app/models/events/user/destroy.rb\n\nclass Events::User::Destroyed \u003c Events::User::BaseEvent\n  payload_attributes :id\nend\n```\n\nAnd we’ll make our `apply` method update the passed-in User’s `deleted` field to `true`:\n```ruby\n# app/models/events/user/destroy.rb\n\nclass Events::User::Destroyed \u003c Events::User::BaseEvent\n  payload_attributes :id\n  \n  def apply(user)\n    user.deleted = true\n    \n    user\n  end\nend\n```\n\nThat’s it—our new Event is done!\n\n\n## Update the `destroy` action in `users_controller`\nIn our `users_controller`, we’ll make our `destroy` action simply create our new `Events::User::Destroyed` event.\n\nThanks to the `find_or_build_aggregate` and `aggregate_id` methods defined in our top-level BaseEvent, this `”Destroyed”` event will look up a User automatically if a `user_id` argument is supplied.\n\nFirst, let’s add `id` to the list of strong params in `user_params`:\n```ruby\n# app/controllers/users_controller.rb\n\nprivate def user_params\n  params.require(:user).permit(:name, :email, :password, :id)\nend\n```\n\nNow, our controller’s `destroy` action can accept a `user_params` that has the necessary `id`. We’ll also use `user_params[:id]` so the event can look up our target User’s record:\n```ruby\n# app/controllers/users_controller.rb\n\ndef destroy\n  Events::User::Destroyed.create(user_id: user_params[:id], payload: user_params)\nend\n```\n\nWe’re ready to go ahead and test with Insomnia!\n\n## Test a DELETE request in Insomnia\nLet’s fire up `rails s`. \n\nOver in Insomnia, create a new request called `Destroy User` and make it a DELETE:\n\n![Insomnia page showing new Destroy User being set to type DELETE](https://dev-to-uploads.s3.amazonaws.com/i/gpddvf8nf4ccuf5orhyh.png)\n\nSet its target URL to `localhost:3000/users/destroy`:\n\n![Insomnia page showing DELETE request's target URL](https://dev-to-uploads.s3.amazonaws.com/i/j6ffh9x0nmfjivrz87bo.png)\n\nSet the Body type to JSON, and add a hash with a `”user”` key pointing to a hash containing the `”id”`:\n\n![Insomnia page showing DELETE request's JSON body with a user id](https://dev-to-uploads.s3.amazonaws.com/i/w3kq02av0bgb0nvmt3p2.png)\n\nHit Send, and check the database to see if the Event was created:\n\n![Postico user_events table showing new Destroyed event record, overlaid on Insomnia request](https://dev-to-uploads.s3.amazonaws.com/i/tqiyqf7jdsac9oaau0a7.png)\n\nAnd finally, let’s check the database to see if our User has `deleted` set to `true`:\n\n![Postico users table showing only User record with deleted field set to true](https://dev-to-uploads.s3.amazonaws.com/i/ciri8xikzflbhjhhvx4m.png)\n\nPerfect! We get to keep our User record, but also have it be `deleted`—we’re having our cake, and eating it too!\n\n![screenshot of cake from video game Portal](https://dev-to-uploads.s3.amazonaws.com/i/bruoty5t9v98as079fut.jpg)\n_And that's no lie!_\n\n**That’s all it takes to add a new event to our event sourcing system!**\n\n\n# Conclusion\nWow, we covered a lot of ground! Let’s recap the steps we took to implement our event sourcing system:\n-   Create a new Rails app, with a User model and controller, and PostgreSQL for the database\n-   Create an `Events::BaseEvent` class in `app/models/events` to handle Event logic:\n\t-   Looking up or creating aggregates (Users)\n\t-   Creating getters and setters for `payload_attributes`\n\t-   Inferring its own `event_type`\n\t-   Hooks for automatically applying changes and saving to the database\n-   Create a `user_events` table migration\n-   Create an `Events::User::BaseEvent` to save all Events in its `Events::User::` namespace to the `user_events` table\n-   Create an `Events::User::Created` event that will apply `user_params` to a new User instance\n-   Create an `Events::User::Destroyed` event that will look up at User by `id` and set its `deleted` field to `true`\n\nThis minimal system allows us to do the following:\n-   Have a record of events that create and destroy Users\n-   Keep all User data permanently, and still have the ability to scope the `deleted` ones as needed\n-   A pattern that allows us to easily add new Events that will be saved to the same `user_events` table\n\n\n# Next Up\nWe have a lot more we can do to improve our event sourcing system, especially around security and data validations! In the next article, we will cover:\n-   Storing sensitive information safely in Event `payloads`, such as passwords\n-   Wrapping creating Events in `Commands`, [per Kickstarter’s example]([https://github.com/pcreux/event-sourcing-rails-todo-app-demo/blob/master/app/models/lib/command.rb](https://github.com/pcreux/event-sourcing-rails-todo-app-demo/blob/master/app/models/lib/command.rb))\n-   Adding validations to `Commands`\n\n\n\n# References\nSpecial thanks to [Philippe Creux](https://kickstarter.engineering/@pcreux) and [Kickstarter](https://kickstarter.engineering/event-sourcing-made-simple-4a2625113224) for sharing [their Event Sourcing example](https://github.com/pcreux/event-sourcing-rails-todo-app-demo).\n\nThanks to [Martin Fowler](https://martinfowler.com/) for his [important writings](https://martinfowler.com/articles/201701-event-driven.html) on [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html).\n\nThanks to [Arkency](https://arkency.com/) for their great work with [the RailsEventStore library](https://github.com/RailsEventStore/rails_event_store).\n\nAnd finally, thanks to fellow Dev.to user [Alfredo Motta](https://dev.to/mottalrd) for [sharing about this years ago](https://dev.to/mottalrd/an-introduction-to-event-sourcing-for-rubyists-41e5) (and keeping it up for me to catch up on!).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisalevine%2Fevent-sourcing-user-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisalevine%2Fevent-sourcing-user-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisalevine%2Fevent-sourcing-user-app/lists"}