Authentication events and commands
Tags: opensource, programming, cqrs, ruby
Published: 2022-08-22 19:15:52

The last few days I didn't write too much new code on the [Ecommerce](https://github.com/RailsEventStore/ecommerce) project.

However, that's the beauty of open source! Regardless of me not being active, there were 2 new contributions, how lovely!

One of the new features which is being worked on is [displaying a list of Shipments](https://github.com/RailsEventStore/ecommerce/pull/176). At the moment of writing this, it's not yet merged.

I will focus on the Authentication feature.

This new code (a new bounded context) [was implemented by Paweł Strzałkowski](https://github.com/RailsEventStore/ecommerce/pull/175) (thanks Paweł!) and this was his first contribution.

Overall the goal is to allow clients to properly log in.

Obviously, we could go with Devise and have it done in 10 minutes. However, we all know how it ends. It ends with a User class and its 100 attributes accumulated over time.

Given that the whole project is fully DDD, CQRS and Event Sourcing - it was part of the goal to have authentication implemented in this fashion.

The idea of Authentication as a separate Bounded Context first came to me from the "Implementing Domain Driven Design" book by Vaughn Vernon. It's commonly known as the red book. 

Vaughn called it Identity & Access. We're going for `Authentication` for now.

So far, we're implementing BCs as new directories and separate Ruby gems. They each have their own tests, their own Makefile, their own build system, their own mutation coverage.

OK, let's start with the input to the Authentication module - the commands:

At first, there's no account, so we need to register one:

```ruby
module Authentication
  class RegisterAccount < Infra::Command
    attribute :account_id, Infra::Types::UUID
    alias aggregate_id account_id
  end
end
```

As you see, it doesn't really have username or password, it comes as separate commands:

```ruby
module Authentication
  class SetLogin < Infra::Command
    attribute :account_id, Infra::Types::UUID
    attribute :login, Infra::Types::String
    alias aggregate_id account_id
  end
end
```

```ruby
module Authentication
  class SetPasswordHash < Infra::Command
    attribute :account_id, Infra::Types::UUID
    attribute :password_hash, Infra::Types::String
    alias aggregate_id account_id
  end
end
```

In the end we can connect such an account to a Customer:

```ruby
module Authentication
  class ConnectAccountToClient < Infra::Command
    attribute :account_id, Infra::Types::UUID
    attribute :client_id, Infra::Types::UUID
    alias aggregate_id account_id
  end
end
```

Such a design is very flexible. We can have clients without an account. Also, we can have clients with multiple accounts.

We also have a similar set of events, also very granular, allowing for flexibility:

```ruby
module Authentication
  class AccountRegistered < Infra::Event
    attribute :account_id, Infra::Types::UUID
  end
  
  class LoginSet < Infra::Event
    attribute :account_id, Infra::Types::UUID
    attribute :login, Infra::Types::String
  end

  class PasswordHashSet < Infra::Event
    attribute :account_id, Infra::Types::UUID
    attribute :password_hash, Infra::Types::String
  end

  class AccountConnectedToClient < Infra::Event
    attribute :account_id, Infra::Types::UUID
    attribute :client_id, Infra::Types::UUID
  end
end
```

How does it happen that a command is issued and then the module issues an event?

We have a layer of command handlers:

```ruby
module Authentication
  class RegisterAccountHandler
    def initialize(event_store)
      @repository = Infra::AggregateRootRepository.new(event_store)
    end

    def call(command)
      @repository.with_aggregate(Account, command.aggregate_id) do |account|
        account.register
      end
    end
  end
```

Here is the part where the infra part is injected - the `event_store`. We need for retrieving the events, so that the object (here: Account) can be event sourced (loaded from events).

It all happens in the `AggregateRootRepository.new` so we don't have to care about this. 

The object that is loaded is called an aggregate.
We call a `register` method on it.

Let's take a look at the whole aggregate class:

```ruby
module Authentication
  class Account
    include AggregateRoot

    AlreadyRegistered = Class.new(StandardError)

    def initialize(id)
      @id = id
    end

    def register
      raise AlreadyRegistered if @registered

      apply AccountRegistered.new(data: { account_id: @id })
    end

    def set_login(login)
      apply LoginSet.new(data: { account_id: @id, login: login })
    end

    def set_password_hash(password_hash)
      apply PasswordHashSet.new(data: { account_id: @id, password_hash: password_hash })
    end

    def connect_client(client_id)
      apply AccountConnectedToClient.new(data: { account_id: @id, client_id: client_id })
    end

    on AccountRegistered do |event|
      @registered = true
    end

    on LoginSet do |event|
      @login = event.data[:login]
    end

    on PasswordHashSet do |event|
      @password_hash = event.data[:password_hash]
    end

    on AccountConnectedToClient do |event|
      @client_id = event.data[:client_id]
    end
  end
end
```

The pattern which is used here, is called `AggregateRoot` - it mixed the business logic with the messaging (events) part. It's all simplified with our `AggregateRoot` module which is injected.

As you can see, so far there's not much actual logic. The only rule is to not allow this Account to be registered more than once.

Keeping all this data in one aggregate is a one possible design. It's simple and very familiar to CRUD designs.

With the current requirements, it's good enough. Maybe in the future we can go with even smaller aggregates - once more fancy requirements come.

The domain level is always surrounded with application layer. In a way the app layer protects the domain layer. It knows how to use it. 

Certain requirements can be kept at the app layer.

For example, looking at this code, we may ask: 

- How are we protected against connecting the same account to multiple clients?

Let's keep in mind that the feature is still in progress and the requirements are created by us. It's a bit vague now.

At the moment, this code is only used in database seeds, from the app layer:

```ruby
[
  ["BigCorp Ltd", "bigcorp", "12345"],
  ["MegaTron Gmbh", "megatron", "qwerty"],
  ["Arkency", 'arkency', 'qwe123']
].each do |name, login, password|
  account_id = SecureRandom.uuid
  customer_id = SecureRandom.uuid
  password_hash = Digest::SHA256.hexdigest(password)

  command_bus.call(
    Crm::RegisterCustomer.new(customer_id: customer_id, name: name)
  )

  command_bus.call(
    Authentication::RegisterAccount.new(account_id: account_id)
  )

  command_bus.call(
    Authentication::SetLogin.new(account_id: account_id, login: login)
  )

  command_bus.call(
    Authentication::SetPasswordHash.new(account_id: account_id, password_hash: password_hash)
  )

  command_bus.call(
    Authentication::ConnectAccountToClient.new(account_id: account_id, client_id: customer_id)
  )
end
```


There's no UI for that, so the accounts are not really used in any way.

Still, a nice step forward in this Authentication area. It opens the door to implementing the UI and the app layer (controllers, read models).

As you can see, apart from the `Customer` reference (maybe we should rename it to `User` to stick to the Ubiquitous Language of Authentication?) - it's mostly generic code. I hope that this BC together with read models which will be created can become a reusable library which could be a good alternative to Devise in those projects which want to go the DDD/CQRS path.