<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrzej Krzywda</title>
    <description>The latest articles on DEV Community by Andrzej Krzywda (@andrzejkrzywda).</description>
    <link>https://dev.to/andrzejkrzywda</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F193379%2F993b4eab-5b98-4a70-ab37-93c52bc644e6.jpg</url>
      <title>DEV Community: Andrzej Krzywda</title>
      <link>https://dev.to/andrzejkrzywda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrzejkrzywda"/>
    <language>en</language>
    <item>
      <title>Authentication events and commands</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Mon, 22 Aug 2022 19:15:52 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/authentication-events-and-commands-5670</link>
      <guid>https://dev.to/andrzejkrzywda/authentication-events-and-commands-5670</guid>
      <description>&lt;p&gt;The last few days I didn't write too much new code on the &lt;a href="https://github.com/RailsEventStore/ecommerce"&gt;Ecommerce&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;However, that's the beauty of open source! Regardless of me not being active, there were 2 new contributions, how lovely!&lt;/p&gt;

&lt;p&gt;One of the new features which is being worked on is &lt;a href="https://github.com/RailsEventStore/ecommerce/pull/176"&gt;displaying a list of Shipments&lt;/a&gt;. At the moment of writing this, it's not yet merged.&lt;/p&gt;

&lt;p&gt;I will focus on the Authentication feature.&lt;/p&gt;

&lt;p&gt;This new code (a new bounded context) &lt;a href="https://github.com/RailsEventStore/ecommerce/pull/175"&gt;was implemented by Paweł Strzałkowski&lt;/a&gt; (thanks Paweł!) and this was his first contribution.&lt;/p&gt;

&lt;p&gt;Overall the goal is to allow clients to properly log in.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;Vaughn called it Identity &amp;amp; Access. We're going for &lt;code&gt;Authentication&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;OK, let's start with the input to the Authentication module - the commands:&lt;/p&gt;

&lt;p&gt;At first, there's no account, so we need to register one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegisterAccount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Command&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, it doesn't really have username or password, it comes as separate commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SetLogin&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Command&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SetPasswordHash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Command&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:password_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end we can connect such an account to a Customer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConnectAccountToClient&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Command&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such a design is very flexible. We can have clients without an account. Also, we can have clients with multiple accounts.&lt;/p&gt;

&lt;p&gt;We also have a similar set of events, also very granular, allowing for flexibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountRegistered&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Event&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginSet&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Event&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PasswordHashSet&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Event&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:password_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountConnectedToClient&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Event&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How does it happen that a command is issued and then the module issues an event?&lt;/p&gt;

&lt;p&gt;We have a layer of command handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegisterAccountHandler&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Infra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AggregateRootRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the part where the infra part is injected - the &lt;code&gt;event_store&lt;/code&gt;. We need for retrieving the events, so that the object (here: Account) can be event sourced (loaded from events).&lt;/p&gt;

&lt;p&gt;It all happens in the &lt;code&gt;AggregateRootRepository.new&lt;/code&gt; so we don't have to care about this. &lt;/p&gt;

&lt;p&gt;The object that is loaded is called an aggregate.&lt;br&gt;
We call a &lt;code&gt;register&lt;/code&gt; method on it.&lt;/p&gt;

&lt;p&gt;Let's take a look at the whole aggregate class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authentication&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;AggregateRoot&lt;/span&gt;

    &lt;span class="no"&gt;AlreadyRegistered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;AlreadyRegistered&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@registered&lt;/span&gt;

      &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="no"&gt;AccountRegistered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="vi"&gt;@id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="no"&gt;LoginSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="vi"&gt;@id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;login: &lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_password_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="no"&gt;PasswordHashSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="vi"&gt;@id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;password_hash: &lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="no"&gt;AccountConnectedToClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="vi"&gt;@id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;client_id: &lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="no"&gt;AccountRegistered&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@registered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="no"&gt;LoginSet&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="no"&gt;PasswordHashSet&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@password_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:password_hash&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="no"&gt;AccountConnectedToClient&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern which is used here, is called &lt;code&gt;AggregateRoot&lt;/code&gt; - it mixed the business logic with the messaging (events) part. It's all simplified with our &lt;code&gt;AggregateRoot&lt;/code&gt; module which is injected.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Keeping all this data in one aggregate is a one possible design. It's simple and very familiar to CRUD designs.&lt;/p&gt;

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

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;Certain requirements can be kept at the app layer.&lt;/p&gt;

&lt;p&gt;For example, looking at this code, we may ask: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How are we protected against connecting the same account to multiple clients?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;At the moment, this code is only used in database seeds, from the app layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"BigCorp Ltd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bigcorp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"MegaTron Gmbh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"megatron"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"qwerty"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Arkency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'arkency'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'qwe123'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
  &lt;span class="n"&gt;password_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Crm&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RegisterCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;customer_id: &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Authentication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RegisterAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Authentication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetLogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;login: &lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Authentication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetPasswordHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;password_hash: &lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Authentication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectAccountToClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;client_id: &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no UI for that, so the accounts are not really used in any way.&lt;/p&gt;

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

&lt;p&gt;As you can see, apart from the &lt;code&gt;Customer&lt;/code&gt; reference (maybe we should rename it to &lt;code&gt;User&lt;/code&gt; 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.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>programming</category>
      <category>cqrs</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Exploring a generic read model API</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Mon, 15 Aug 2022 23:57:29 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/exploring-a-generic-read-model-api-56ck</link>
      <guid>https://dev.to/andrzejkrzywda/exploring-a-generic-read-model-api-56ck</guid>
      <description>&lt;p&gt;Arkency Ecommerce follows the CQRS pattern and creates separate modules for "reads".&lt;/p&gt;

&lt;p&gt;We call them read models.&lt;/p&gt;

&lt;p&gt;The simplest read models consist of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one or more database table&lt;/li&gt;
&lt;li&gt;some configuration code which maps events to columns in the table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, I was about to create a read model. A public offer of products presented to the client.&lt;/p&gt;

&lt;p&gt;We already have a &lt;code&gt;Products&lt;/code&gt; read model, used on the admin/sales panel.&lt;/p&gt;

&lt;p&gt;At first they seem to be almost the same and it's tempting to just reuse it.&lt;/p&gt;

&lt;p&gt;This is an often temptation in CQRS. This temptation is obvious in CRUD systems. There's one way of retrieving products, how complex can it be, right?&lt;/p&gt;

&lt;p&gt;However, even in our simple feature set it's already visible that they contain different columns.&lt;/p&gt;

&lt;p&gt;They serve different needs.&lt;/p&gt;

&lt;p&gt;What am I really saving by reusing it?&lt;/p&gt;

&lt;p&gt;The only risk is if we start having some calculations to show price (very likely, but is it part of read models?), then we can forget about some of them.&lt;/p&gt;

&lt;p&gt;Anyway.&lt;/p&gt;

&lt;p&gt;I looked at the current read model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Products&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;register_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ProductCatalog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ProductRegistered&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;change_stock_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StockLevelChanged&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;set_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PriceSet&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;set_vat_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Taxes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VatRateSet&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_vat_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:vat_rate_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:vat_rate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change_stock_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:stock_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:stock_level&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not about you, but it screams to me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THIS CODE IS MOSTLY DATA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's basically mapping declarations, with some subtleties. All on top of ActiveRecord.&lt;/p&gt;

&lt;p&gt;Just to show you the db schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: :uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"gen_random_uuid()"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt; &lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;precision: &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scale: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"stock_level"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="s2"&gt;"registered_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;precision: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"vat_rate_code"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(and I'm adding to my TODO to check if the &lt;code&gt;registered_at&lt;/code&gt; column is somehow magically used or it can be deleted)&lt;/p&gt;

&lt;p&gt;Then I looked at how it's tested.&lt;/p&gt;

&lt;p&gt;While we maintain almost 100% mutation coverage at the domain level, we don't share a similar quality at the app level (controllers, read models).&lt;/p&gt;

&lt;p&gt;I noticed that this read model doesn't have unit tests. It's tested only via integration test, if anything. That's sad. I need to improve on this.&lt;/p&gt;

&lt;p&gt;But then a second thought.&lt;/p&gt;

&lt;p&gt;If I make some mechanical refactorings (the ones which can be done without full test coverage, then this code will become mostly declarations or some kind of DSL API.&lt;/p&gt;

&lt;p&gt;If it really happens, then I should the resulting "framework" which will be extracted instead of the usages. &lt;/p&gt;

&lt;p&gt;The issue with testing declarative code is that it's usually a "typotest", repeating what's in the production code.&lt;/p&gt;

&lt;p&gt;Long story short, this is my first attempt to make the code more declarative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Products&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@cqrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cqrs&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
      &lt;span class="vi"&gt;@cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;register_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ProductCatalog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ProductRegistered&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StockLevelChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stock_level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PriceSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;copy_nested_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Taxes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VatRateSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:vat_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:vat_rate_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While those 2 lines are quite elegant to me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StockLevelChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stock_level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PriceSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They look nice, because they (by accident) follow the convention of matching the event attribute with the column name.&lt;/p&gt;

&lt;p&gt;The other lines still need some love.&lt;/p&gt;

&lt;p&gt;First, creating a record here requires a name, it's not just empty constructor. It was hard for me to find some generalization of this code.&lt;/p&gt;

&lt;p&gt;The last line was the edge case. It happens to have nested data in the event. Also it doesn't match the column name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy_nested_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Taxes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VatRateSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:vat_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:vat_rate_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and here is the remaining supporting code to make it all work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;copy_event_attribute_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy_nested_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_event_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nested_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;copy_nested_event_attribute_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_event_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nested_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy_event_attribute_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_attribute&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy_nested_event_attribute_to_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_event_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nested_attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_event_attribute&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nested_attribute&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code should still become more generic.&lt;br&gt;
We need to un-hardcode the ActiveRecord class name &lt;code&gt;Product&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And just to remind you. My initial goal was to create a new read model. What I'm doing here is a preparatory refactoring. After this, I expect my new read model to be implemented in only few lines of declarations.&lt;/p&gt;

&lt;p&gt;We will see how it goes :)&lt;/p&gt;

&lt;p&gt;Here is the commit with those changes:&lt;br&gt;
&lt;a href="https://github.com/RailsEventStore/ecommerce/commit/9e42950fc7eb34257938b0501fa6e50733d0d568"&gt;https://github.com/RailsEventStore/ecommerce/commit/9e42950fc7eb34257938b0501fa6e50733d0d568&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading ❤️&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>programming</category>
      <category>webdev</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Ecommerce - the modularisation ideas</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Thu, 11 Aug 2022 14:33:00 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/ecommerce-the-modularisation-ideas-4b4d</link>
      <guid>https://dev.to/andrzejkrzywda/ecommerce-the-modularisation-ideas-4b4d</guid>
      <description>&lt;p&gt;At the &lt;a href="https://github.com/RailsEventStore/ecommerce" rel="noopener noreferrer"&gt;Ecommerce project&lt;/a&gt; we have a main split between the app layer and the domain layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7h21kibqy2p8u5dl62h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7h21kibqy2p8u5dl62h.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We aim for the domains to be reusable. &lt;br&gt;
The applications are specific, not meant to be reusable, at least that's not the main goal.&lt;/p&gt;

&lt;p&gt;I'd like to share my current thinking snapshot of what I think could help us achieve nice modularity and reusability.&lt;/p&gt;

&lt;p&gt;What makes a domain?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commands as the only input layer&lt;/li&gt;
&lt;li&gt;events as the only output layer&lt;/li&gt;
&lt;li&gt;aggregates as internal mechanism which maintains the state transitions

&lt;ul&gt;
&lt;li&gt;they could be objects or functions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In theory, that's all.&lt;/p&gt;

&lt;p&gt;In practice, you sometimes need to have something more. Sometimes you will have a read model which exists only for the purpose of this BC (bounded context) to read from. For example, Pricing BC may need to maintains its own internal Pricing Catalog, which to read prices from.&lt;/p&gt;

&lt;p&gt;Given the above, we want to &lt;strong&gt;keep the commands and events as granular as possible&lt;/strong&gt;. We want to allow all kind of reusability on top of this.&lt;/p&gt;

&lt;p&gt;Some apps may want to have nicer/bigger commands or events. That's great.&lt;/p&gt;

&lt;p&gt;They should use the EventSummaryBuilder pattern to &lt;strong&gt;build from the small events and provide bigger events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Similar thing with commands, they may want to build bigger commands - that's great, let's just show how those big commands map onto the existing small commands.&lt;/p&gt;

&lt;p&gt;Those builders, at least that's my current thinking, are not part of the BC. They are part of the app layer, because they are a tweak for the application. In a way they are the facade which could/should be maintain by the application, as the domain code consumer.&lt;/p&gt;

&lt;p&gt;We can provide them as something reusable, but that's different "blocks".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BCs alone are useful, but they don't create the Feature.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A feature is a set of building blocks which together form a functional and working unit at the application layer combined with the domain/BC.&lt;/p&gt;

&lt;p&gt;Let's look at Invoicing.&lt;/p&gt;

&lt;p&gt;There's Invoicing BC with its events and commands.&lt;/p&gt;

&lt;p&gt;There's a &lt;strong&gt;process manager&lt;/strong&gt; which builds the Invoicing state via reacting to some Pricing events. This is &lt;strong&gt;application layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's obviously a way to display the invoice for the customer. &lt;br&gt;
That's a &lt;strong&gt;read model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's also a way to display the invoice to the admin, usually different than the one for clients. That's another read model.&lt;/p&gt;

&lt;p&gt;Those read models have their own storages - they should own it. The migrations should belong to the read models.&lt;br&gt;
The html/json renderers can also belong to read models.&lt;/p&gt;

&lt;p&gt;There's this gray area too. &lt;br&gt;
How useful can the BCs be without the accompanying BCs?&lt;/p&gt;

&lt;p&gt;Can we imagine the Invoicing BC to work fine without the current Pricing?&lt;/p&gt;

&lt;p&gt;I think it should be possible.&lt;br&gt;
One hypothetical scenario is to create a typical Invoicing application, without any ecommerce surroundings. &lt;/p&gt;

&lt;p&gt;That's overall the general idea on how things could work.&lt;/p&gt;

&lt;p&gt;Things may change, once we have more apps and more reusability which will show which parts of this vision are fragile.&lt;/p&gt;

&lt;p&gt;Thoughts?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ruby</category>
      <category>opensource</category>
      <category>ddd</category>
    </item>
    <item>
      <title>Html templates as Ruby code</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Wed, 10 Aug 2022 01:24:33 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/html-templates-as-ruby-code-3m9o</link>
      <guid>https://dev.to/andrzejkrzywda/html-templates-as-ruby-code-3m9o</guid>
      <description>&lt;p&gt;At the &lt;a href="https://github.com/RailsEventStore/ecommerce"&gt;Arkency Ecommerce project&lt;/a&gt;, we went for the server rendered views strategy. Instead of dealing with JavaScript frameworks we have it all under the Rails umbrella. &lt;/p&gt;

&lt;p&gt;Given the project is very "business" oriented, there's less need to have a fancy JS UI.&lt;/p&gt;

&lt;p&gt;One typical view was our Client Panel login screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"max-w-6xl mx-auto py-6 sm:px-6 lg:px-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: :post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block font-bold"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Client
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;select_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options_from_collection_for_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@clients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;button_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a usual Rails view called ERB, where it's html with some special Ruby snippets wrapped with &amp;lt;%= %&amp;gt;.&lt;/p&gt;

&lt;p&gt;Such views are fine and every Rails dev knows how to modularise it into partials, how to use helpers etc.&lt;/p&gt;

&lt;p&gt;However, over time Rails views tend to accumulate some "logic". It's hard to avoid some &lt;code&gt;if&lt;/code&gt; statements. The views are more Ruby than HTML.&lt;/p&gt;

&lt;p&gt;That's why I decided to try another approach in the Client Panel part of our application.&lt;/p&gt;

&lt;p&gt;Let's try to write those views in Ruby and make it generate HTML.&lt;/p&gt;

&lt;p&gt;There are obvious cons to this approach - it's no longer HTML so it's harder to get frontend people to tweak it. &lt;br&gt;
Still, the pros might be worth experimenting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Login&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Arbre&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Arbre&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_context&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ClientOrders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"max-w-6xl mx-auto py-6 sm:px-6 lg:px-8"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;safe_join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;text_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: :post&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;safe_join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;select_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options_from_collection_for_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;button_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's different, yet similar.&lt;br&gt;
I'm using the &lt;code&gt;[arbre](https://activeadmin.github.io/arbre/)&lt;/code&gt; library here. There are others, but this one was already battle-tested in one of our projects.&lt;/p&gt;

&lt;p&gt;There are some gotchas here. While the Ruby code looks almost like a direct translation from HTML, there are differences.&lt;/p&gt;

&lt;p&gt;Note the &lt;code&gt;safe_join&lt;/code&gt; calls. It's needed whenever we combine more than one "block" of html. &lt;/p&gt;

&lt;p&gt;Another one is &lt;code&gt;text_node&lt;/code&gt; which is often needed for the Rails helpers. Otherwise, the tags might not be rendered.&lt;/p&gt;

&lt;p&gt;Now the controller looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Client&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
    &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="s2"&gt;"client_panel"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="no"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's too early to say if it's worth it. I will turn some more views in this area and try to make the views more complex. Then I will see if it's really easier to deal with the view logic in pure Ruby or ERB approach is better.&lt;/p&gt;

&lt;p&gt;One hope which I have with this evolution is that now I can actually test the code as any other Ruby class. Also, it's now possible to measure the test coverage.&lt;/p&gt;

&lt;p&gt;What is your idea about this approach?&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Implementing Authentication in tiny steps</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Thu, 04 Aug 2022 13:07:00 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/implementing-authentication-in-tiny-steps-jhn</link>
      <guid>https://dev.to/andrzejkrzywda/implementing-authentication-in-tiny-steps-jhn</guid>
      <description>&lt;p&gt;When we started the project which we currently call "Arkency Ecommerce", under the RailsEventStore umbrella - it actually started as an Ordering Management System (OMS), more than actual ecommerce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I always thought that OMS is an obvious part of ecommerce platforms&lt;/strong&gt;. Recently I learnt it's not always the case, and even &lt;a href="https://www.shopify.com/enterprise/order-management-system-oms"&gt;Shopify recommend&lt;/a&gt; looking at some other OMS systems as addition to their ecommerce.&lt;/p&gt;

&lt;p&gt;Interesting.&lt;/p&gt;

&lt;p&gt;Anyway, when our project grew, we were focused on the "Sales Panel" or "OMS panel". The user was probably a sales person. There was no authentication for that. We can assume that it's for internal use of the company hidden in the intranet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fqu9pZXz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5351r83soqrq9o38uzea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fqu9pZXz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5351r83soqrq9o38uzea.png" alt="OMS view" width="880" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After some time, we have created the "Client View". At first it was without authentication too. We only added a "select list" to choose which client you are "logged in" as.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWMUNmYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1dr71rcdkgdatycnl0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWMUNmYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1dr71rcdkgdatycnl0z.png" alt="login" width="458" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, even then, we just used the URL param to remember which client we're logged in as. Also, the functionality was and still is very limited. It was just a list of orders. &lt;/p&gt;

&lt;p&gt;There was also a detailed order view, but I just removed it. The reason I removed it, was because it didn't have tests and it didn't use its own read model, just borrowed from the OSM OrdersList read model. It doesn't sound like a big deal, but it actually is. We don't want changes to OSM to interfere with the Client Panel.&lt;/p&gt;

&lt;p&gt;One of my recent changes was to extend the authentication with cookies storage, while still not having passwords yet.&lt;/p&gt;

&lt;p&gt;I like the concept of tiny steps or as I like to call it "start from the middle".&lt;/p&gt;

&lt;p&gt;We started without authentication, but we focused on how it would look like. After "seeing it"/"feeling it", we could decide what next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pl8TFSRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9u9h1kq166c8lec2wltp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pl8TFSRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9u9h1kq166c8lec2wltp.png" alt="client panel" width="880" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the luxury of open source codebases, not limited to deadlines and clients. &lt;/p&gt;

&lt;p&gt;Small steps allow for two perspectives. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code perspective&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We could start with some code hacks (coupling of read models) and buy some time to think how it should be reflected in the architecture. Only when we feel what smells we can come up with better architecture.&lt;br&gt;
In this case, it was a realisation that the "client panel" is like a separate app. At the application layer it should be reflected in some way. I strongly believe in 2 levels of architectural split in typical web apps.&lt;br&gt;
One level is app layer. The second one is domain layer. &lt;/p&gt;

&lt;p&gt;Often people confuse those two and try to merge them. Whatever is the app split, becomes the domain split. This is unfortunate as it's almost not possible to do such one-dimension split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI perspective&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Playing with the UI allows to rethink where we are with the features and what are the next stories to implement. Using the UI opens me in thinking what are the next tiny steps to add. &lt;br&gt;
Note that the authentication is not the most important feature of this area. It's crucial once we go live, but before that we can hide it behind a feature flag if we want. We all know how to build authentication. It's not the risky area. But we don't know what to put in the Client Panel exactly for it to be usable. This is more risky. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The current codebase&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Client&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
    &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="s2"&gt;"client_panel"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
      &lt;span class="vi"&gt;@clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ClientOrders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"clients/index"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;
      &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;client_orders_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logout&lt;/span&gt;
      &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;clients_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Client&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

    &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="s1"&gt;'client_panel'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="no"&gt;ClientOrders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrdersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, it's only setting the cookies, without any user/password checks.&lt;/p&gt;

&lt;p&gt;This will be next story to implement, to allow for using passwords for logging in. What's interesting in this "tiny steps" approach is that we can start without even allowing for choosing the password. From the users perspective we can start with the idea of generating the passwords for the client, creating their accounts and only then (later) allowing them to choose/change passwords. That's like 3 other "stories" to implement.&lt;/p&gt;

&lt;p&gt;As for the code, some of you may find this bit interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="no"&gt;ClientOrders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrdersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:client_id&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went for the Rails view implementation with an unusual approach - writing the templated in Ruby instead of ERB. &lt;/p&gt;

&lt;p&gt;But that's a topic for another blogpost.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>rails</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Unblocking my blogging - recent progress at Arkency Ecommerce project</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Tue, 02 Aug 2022 10:30:54 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/unblocking-my-blogging-recent-progress-at-arkency-ecommerce-project-5b24</link>
      <guid>https://dev.to/andrzejkrzywda/unblocking-my-blogging-recent-progress-at-arkency-ecommerce-project-5b24</guid>
      <description>&lt;p&gt;Recently I've been struggling with a blockade in writing posts. It's not the first time nor the last one, so I learnt to accept it.&lt;/p&gt;

&lt;p&gt;In the past what helped me is just &lt;strong&gt;documenting what I'm doing as blogposts&lt;/strong&gt;. When you document you focus on what you did. It's almost like a written/async standup. When I try to teach or express opinions as a blogpost things are more difficult for me.&lt;/p&gt;

&lt;p&gt;I'm lucky to be able to work on an open source project. Which means I work in public. It also means I can freely share the progress.&lt;/p&gt;

&lt;p&gt;I will probably write some posts here, then move to the Arkency blog.&lt;/p&gt;

&lt;p&gt;So as an attempt to unblock myself, over the next days I'll share a few small things I did recently on the &lt;a href="https://github.com/RailsEventStore/ecommerce"&gt;Arkency Ecommerce&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;The project is an attempt to implement an ecommerce/order-management-system in &lt;strong&gt;Rails but fully using Domain Driven Design, Event Sourcing and Command Query Responsibility Segregation&lt;/strong&gt;. If you don't know those buzzwords, don't worry. It's just difficult names, for simple things. Feel free to ask if something is not clear.&lt;/p&gt;

&lt;p&gt;One recent small issue was open source related.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/RailsEventStore/ecommerce/issues/173"&gt;Someone asked&lt;/a&gt; "what is the license for this project". The problem was that in the past, this repo was just a typical Rails app with its typical structure. The LICENSE file resides in the main directory.&lt;/p&gt;

&lt;p&gt;However, once we went further with the idea of separating the domain code from the Rails app, we changed the directory structure.&lt;/p&gt;

&lt;p&gt;We created &lt;code&gt;rails_application&lt;/code&gt; subdirectory and moved the rails app over there. Then in the main directory we created &lt;code&gt;ecommerce&lt;/code&gt; directory which contains the business modules (aka bounded contexts).&lt;/p&gt;

&lt;p&gt;The LICENSE file was moved together with the Rails app. This means that it was no longer following the convention of keeping the LICENSE file in the main directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ tree -L 1     
.
├── Makefile
├── README.md
├── ecommerce
├── hanami_application
├── infra
├── math
└── rails_application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BTW, we deliberately have the MIT license, so that people can do what they want with this codebase. This project has many educational goals. It's a bit of a challenge too, because of this. When we experiment with new ideas, some people may feel like this is what is now "recommended" and copy the experiments too. I guess that's the risk of doing things in public.&lt;/p&gt;

&lt;p&gt;As a fix for the problem, &lt;a href="https://github.com/RailsEventStore/ecommerce/commit/75bfba06d542dd03ca426c3e23e23da42a44b460"&gt;I moved the LICENSE back&lt;/a&gt; to the main directory. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I was seriously considering writing a test for that.&lt;/strong&gt; This is an important convention to keep the license in the main directory. We're usually very brave with refactorings here. This means we're at risk of regressions of such trivial things too. &lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;p&gt;OK, I think that's enough for an "unblocking" blogpost. I know it was a trivial example. Hopefully this unblocks me and more interesting posts are comming soon 😎&lt;/p&gt;

</description>
      <category>blogging</category>
      <category>rails</category>
      <category>opensource</category>
      <category>ddd</category>
    </item>
    <item>
      <title>What is event-driven? Order#value example</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Tue, 06 Jul 2021 09:44:09 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/what-is-event-driven-order-value-example-gnp</link>
      <guid>https://dev.to/andrzejkrzywda/what-is-event-driven-order-value-example-gnp</guid>
      <description>&lt;p&gt;Some time ago, I have mentioned to you that there is this sample DDD/CQRS application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/RailsEventStore/cqrs-es-sample-with-res"&gt;https://github.com/RailsEventStore/cqrs-es-sample-with-res&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've (Arkency people + &lt;a href="https://arkademy.dev"&gt;Arkademy&lt;/a&gt; subscribers) been working on it and it evolved into something new.&lt;/p&gt;

&lt;p&gt;It's no longer just a sample application. It's now part of a bigger project called:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arkency Ecommerce&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PzRRaTFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0iq30yrkjrbdbnxntuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PzRRaTFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0iq30yrkjrbdbnxntuy.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ecommerce codebase which makes developers happy.&lt;/p&gt;

&lt;p&gt;The goal is exactly this, create such a codebase that will make developers working on it (extending, integration, customising) truly happy.&lt;/p&gt;

&lt;p&gt;I honestly don't know if this will be a framework, or a set of libraries or a customisable scaffold or a code generator. &lt;/p&gt;

&lt;p&gt;Yet.&lt;/p&gt;

&lt;p&gt;What I do know is that the current popular ecommerce platforms are targeting business people (nothing wrong with that), but not always care about programmers (sad).&lt;/p&gt;

&lt;p&gt;You either need to use some overcomplicated codebase and patch it so that it serve your custom needs or you need to make a thousand of API calls because integrating 23 different SaaS providers is apparently the way to go in 2021.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welcome to the integration hell.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What's the alternative?&lt;/p&gt;

&lt;p&gt;A simple, event-driven codebase, consisting of clear and mostly generic bounded contexts like: pricing, payments, ordering, inventory, catalog, crm. &lt;/p&gt;

&lt;p&gt;A number of preset read models available either via API or via Hotwire/Stimulus approach.&lt;/p&gt;

&lt;p&gt;A codebase which you can fork and then customize by implementing the process managers on top of it.&lt;/p&gt;

&lt;p&gt;That's the goal of Arkency Ecommerce. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modulith, not microservices.&lt;/strong&gt;&lt;br&gt;
Events, no coupling.&lt;br&gt;
Event sourcing, no ORM.&lt;br&gt;
Mutation testing coverage, no dead code or untested areas.&lt;/p&gt;

&lt;p&gt;We want to have an easy ecommerce product line. &lt;/p&gt;

&lt;p&gt;Are we there? Nope.&lt;/p&gt;

&lt;p&gt;Is it useful already? Yes.&lt;/p&gt;

&lt;p&gt;To my surprise, there are already 2 companies which are forking this repo to start their efforts on top of that. They do want to contribute back, which is awesome.&lt;/p&gt;

&lt;p&gt;Let me explain the modularity goal.&lt;/p&gt;

&lt;p&gt;Almost every programmer would say that modularisation is a good thing. &lt;/p&gt;

&lt;p&gt;Having modules means having smaller scope. It means easier testing. It also means less bugs in the end. &lt;/p&gt;

&lt;p&gt;It's not so easy to find the right boundary for the modules. Some modules are too small, some are too big. Some modules couple certain things together. &lt;br&gt;
Let's say the modules are more or less "right". &lt;/p&gt;

&lt;p&gt;What is the challenge now? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How to connect modules so that together they create working system? &lt;/p&gt;

&lt;p&gt;Here's what I did with Arkency Ecommerce.&lt;/p&gt;

&lt;p&gt;I split the business logic into business modules following the patter of Domain-Driven Design - Bounded Contexts. &lt;/p&gt;

&lt;p&gt;Then I went for the Read/Write split, following the CQRS approach. &lt;/p&gt;

&lt;p&gt;This resulted in a number of smaller modules: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ordering &lt;/li&gt;
&lt;li&gt;Payments &lt;/li&gt;
&lt;li&gt;Pricing &lt;/li&gt;
&lt;li&gt;ProductCatalog &lt;/li&gt;
&lt;li&gt;CRM &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;plus some UI modules: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders &lt;/li&gt;
&lt;li&gt;Products &lt;/li&gt;
&lt;li&gt;Customers &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do they talk to each other? How do they compose together? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;via EVENTS&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;and COMMANDS&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Each module can be told what to do using commands. &lt;br&gt;
Each module publishes events as a result of executing commands. &lt;/p&gt;

&lt;p&gt;Events and commands are the way to compose modules together.&lt;/p&gt;

&lt;p&gt;Let's look at a simple concept of Order's total value.&lt;/p&gt;

&lt;p&gt;There are several modules involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pricing needs to calculate the value&lt;/li&gt;
&lt;li&gt;Payments needs to know how much to charge&lt;/li&gt;
&lt;li&gt;Orders (the UI modules) needs to display it
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CalculateTotalValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order_id&lt;/span&gt;&lt;span class="p"&gt;)))},&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderSubmitted&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This line of code connects Ordering with Pricing without them knowing about each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetPaymentAmount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)))},&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderTotalValueCalculated&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line connects Pricing with Payments.&lt;/p&gt;

&lt;p&gt;Again, without them knowing about each other.&lt;/p&gt;

&lt;p&gt;This, my friend, is the beauty of event-driven architectures. &lt;/p&gt;

&lt;p&gt;This is the reason I felt in love in event-driven DDD.&lt;/p&gt;

&lt;p&gt;Such modularity, such isolation, such independence of modules - this all create robust software.&lt;/p&gt;

&lt;p&gt;That's the foundation for Arkency Ecommerce.&lt;/p&gt;

&lt;p&gt;You can see the event-driven flow in more details in this 5 minutes video on Arkency Youtube:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2LmdJsCuR5U"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here is the whole modules/events setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cqrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command_bus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Orders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;ProductCatalog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;Crm&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;

    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PaymentProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderExpired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderPaid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentAuthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentReleased&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderConfirmation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentAuthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentCaptured&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ProductCatalog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AssignPriceToProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PriceSet&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CalculateTotalValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order_id&lt;/span&gt;&lt;span class="p"&gt;)))},&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Ordering&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderSubmitted&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cqrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetPaymentAmount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_id: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)))},&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Pricing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderTotalValueCalculated&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me know if any of the concepts here sounded interesting to you - I'd be happy to explain more.&lt;/p&gt;

</description>
      <category>eventdriven</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Querying data from process managers or sagas</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Mon, 26 Apr 2021 09:32:53 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/querying-data-from-process-managers-or-sagas-5jp</link>
      <guid>https://dev.to/andrzejkrzywda/querying-data-from-process-managers-or-sagas-5jp</guid>
      <description>&lt;p&gt;Today I received such a question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hello! Is there ever a time where it’s appropriate to query the read model/projections from inside a saga/process manager? For example:&lt;br&gt;
I have an application/inquiry form that can be submitted by leads, and sometimes they submit it more than once.  I don’t want to create multiple records for a lead if I know I already have them (say by normalized email address). My initial thought was to create a saga around the application submitted event and optionally create the lead if they don’t already exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a question I often receive recently. Here is my answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In general, the answer is yes, sometimes it’s hard to avoid such “queries”.&lt;br&gt;
However, there are some things I’d be careful with:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Try to avoid querying an exisitng read model that is also used for a UI purpose. This comes with a risk that a small change on the UI can result on breaking the process manager (in the worst case have a test which covers such integrations)&lt;/li&gt;
&lt;li&gt;Watch out for too big projections/read-models - certain data (a list of all leads?) can be huge one day.&lt;/li&gt;
&lt;li&gt;Choose the peristence method wisely - for bigger sets of data, projecting from events on the fly can be slow. In that case having a read model backed by a db table is OK&lt;/li&gt;
&lt;li&gt;Sometimes having this need points to a potential design problem (the process manager being too big, similarly as with aggregates) - I don’t think this is your case, but just a word of warning&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>domaindrivendesign</category>
      <category>processmanager</category>
      <category>eventdriven</category>
      <category>programming</category>
    </item>
    <item>
      <title>5 days 5 blogposts - the summary of the Arkademy.dev blogging challenge</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Sun, 25 Apr 2021 09:09:46 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/5-days-5-blogposts-the-summary-of-the-arkademy-dev-blogging-challenge-2nh5</link>
      <guid>https://dev.to/andrzejkrzywda/5-days-5-blogposts-the-summary-of-the-arkademy-dev-blogging-challenge-2nh5</guid>
      <description>&lt;p&gt;5 days 5 blogposts was an idea for a challenge that appeared in my head some time ago. I never remember where ideas come from - was it my idea, someone told me about it? I don't really care - I'm fascinated by ideas regardless of the author.&lt;/p&gt;

&lt;p&gt;Anyway, I disgress here. Which is a common problem in blogging, btw. You sit down and want to write about something and then, BAM, 100 new connections appears and you want to blog about all of them in one blogpost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I believe in blogging as making the world a better place.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I see blogging as a way of documenting what you know, sharing what your learnt. Sometimes, just 3 sentences can "click" in someone's mind. &lt;/p&gt;

&lt;p&gt;I don't buy the counter-argument - "there are too many blogposts already". &lt;/p&gt;

&lt;p&gt;Nope, people are unique. The way I think (and blog) about something is unique too. There are certain people with whom my writing resonates. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don't blog to make millions of people happy, I blog to those 20 people who get inspired/educated from my writing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 5 days 5 blogposts challenge was this idea that we can encourage each other to unblock in blogging. The sense of community and the bond is important here too. That's why it all became easier to organise, once Arkademy has started. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://arkademy.dev/"&gt;Arkademy&lt;/a&gt; is this community of programmers, both very very senior and also very very junior. It includes many Ruby programmers, but is not limited to any special technology. We are a welcoming community and the goal is to create a university-like environment of learning/supporting each other. &lt;/p&gt;

&lt;p&gt;One part of Arkademy is courses (including 2 blogging courses), The other part is communication - also via Discord.&lt;/p&gt;

&lt;p&gt;When Arkademy started - it was possible to start the blogging challenge. The community is already big enough that targeting 5-10 people was a realisting goal. At the same time it's still not too crowdy and it's easy to recognize people by their nicknames.&lt;/p&gt;

&lt;p&gt;The first thing was a blogging conference. This consisted of 4 talks and was like a preludium to the actual challenge. The Arkency bloggers + &lt;a href="http://lingocv.com/"&gt;Mira&lt;/a&gt; (English expert) shared their tips during online talks.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xf78DVZsMP4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It's worth wathing this talk if you're worried about your English.&lt;/p&gt;

&lt;p&gt;After the conference, we have started the challenge.&lt;/p&gt;

&lt;p&gt;During the first day, I was a bit worried - what if no one joins?&lt;/p&gt;

&lt;p&gt;It was a great relief to see the first "submissions". We've used one "Blogging" Discord channel. My goal was to review and motivate in the first days of the challenge. Somehow it happened that I joined the actual challenge too. It was nice to see also that Paweł from Arkency has joined the challenge too.&lt;/p&gt;

&lt;p&gt;You can see all/most blogposts grouped here: &lt;a href="http://5days5blogposts.arkademy.dev/"&gt;http://5days5blogposts.arkademy.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of the people who joined the challenge are people who either never blogged or tried but got stuck and didn't blog for a long time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was so nice to see how they unblock in writing. It was also very motivating how they overcome all the possible problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I hate my writing&lt;/li&gt;
&lt;li&gt;noone is going to read this&lt;/li&gt;
&lt;li&gt;I'm tired, I blogged already yesterday&lt;/li&gt;
&lt;li&gt;I have nothing to write about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But somehom most of us stayed till the end.&lt;/p&gt;

&lt;p&gt;As you can see, the format of the blogposts was varying a lot.&lt;/p&gt;

&lt;p&gt;Some of my lessons here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's hard to write a tutorial-like high-quality blogpost every day. &lt;/li&gt;
&lt;li&gt;Perfectionism is a hard opponent&lt;/li&gt;
&lt;li&gt;Showing code makes blogging much easier&lt;/li&gt;
&lt;li&gt;Sharing opinons may be controversial but is easy to write as you stay true to yourself&lt;/li&gt;
&lt;li&gt;Maintaing the persona of who you write for helps&lt;/li&gt;
&lt;li&gt;Keeping a backlog of blogging ideas helps people&lt;/li&gt;
&lt;li&gt;Trying to use my strengths (somehow simulated by for example Gallup talents) is a good idea, as it helps me with focus&lt;/li&gt;
&lt;li&gt;People like reading my opinions&lt;/li&gt;
&lt;li&gt;A little clickbait in the title can help&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What helped me the most, though was the "team effort".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm an indivudual but I love being part of the team where we can support each other. That's why the success of the &lt;a href="http://blog.arkency.com/"&gt;Arkency blog&lt;/a&gt; (listed as top-10 ruby blogs in the world) - we help each other, we trust each other, we embrace our differences.&lt;/p&gt;

&lt;p&gt;During the challenge, at first I wasn't sure if the "team effect" could help that much. We didn't really know each other that well. &lt;/p&gt;

&lt;p&gt;In the end, it's the group that helped me finish the challenge. In the moments where I wanted to give up it was motivating to see Discord notifications that Mikołaj, Anna, Piotr, Manuel and others have posted their blogposts. &lt;/p&gt;

&lt;p&gt;My Gallup's Maximizer (the talent to see a true potential in other people) is very happy. I've seen people transitioning from not bloggging at all to be almost addicted to blogging after only 5 days. &lt;/p&gt;

&lt;p&gt;This is the kind of challenge I dreamt of.&lt;/p&gt;

&lt;p&gt;Thank you to all the people who started the Arkademy Blogging Challenge. You're great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm already motivated to prepare and organize other such challenges at Arkademy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;work on a fresh Rails app for 5 days&lt;/li&gt;
&lt;li&gt;implementing typical ecommerce challenge as a programming kata&lt;/li&gt;
&lt;li&gt;designing a good architecture for existing systems - architectural kata&lt;/li&gt;
&lt;li&gt;React component a day&lt;/li&gt;
&lt;li&gt;Improving test coverage via mutation testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that's a topic for another blogpost. &lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>5days5blogposts</category>
      <category>watercooler</category>
      <category>writing</category>
      <category>arkademy</category>
    </item>
    <item>
      <title>Fixing the node-sass problem in Rails - node downgrade helps</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Fri, 23 Apr 2021 20:42:05 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/fixing-the-node-sass-problem-in-rails-node-downgrade-helps-16lh</link>
      <guid>https://dev.to/andrzejkrzywda/fixing-the-node-sass-problem-in-rails-node-downgrade-helps-16lh</guid>
      <description>&lt;p&gt;Today I've had one of those irritating problems with Rails that I know one day I will have again. So, future me when you google it, here is the fix, you're welcome :)&lt;/p&gt;

&lt;p&gt;The problem shows like this during &lt;code&gt;rails new new_app&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/node-sass: Command failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 error generated.
make: *** [Release/obj.target/binding/src/binding.o] Error 1
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/Users/andrzej/apps/stimulus_infinite_scroll/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack     at ChildProcess.emit (node:events:365:28)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (node:internal/child_process:290:12)
gyp ERR! System Darwin 19.6.0
gyp ERR! command "/usr/local/Cellar/node/16.0.0/bin/node" "/Users/andrzej/apps/stimulus_infinite_scroll/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /Users/andrzej/apps/stimulus_infinite_scroll/node_modules/node-sass
gyp ERR! node -v v16.0.0
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the node version used is &lt;code&gt;16.0.0&lt;/code&gt;. At the moment of writing this post, it's a relatively new version and not everything works fine with this. &lt;/p&gt;

&lt;p&gt;The solution? Downgrade node.&lt;/p&gt;

&lt;p&gt;In my case I did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g n
sudo n 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that's it. &lt;/p&gt;

&lt;p&gt;A possible alternative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew remove node
brew install node@14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this helped you, let me know in the comments, so that I believe in value of such posts even more ;)&lt;/p&gt;

</description>
      <category>5days5blogposts</category>
      <category>rails</category>
      <category>node</category>
    </item>
    <item>
      <title>From Rails scaffold listing to Hotwire infinite scroll</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Thu, 22 Apr 2021 19:06:01 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/from-rails-scaffold-listing-to-hotwire-infinite-scroll-3273</link>
      <guid>https://dev.to/andrzejkrzywda/from-rails-scaffold-listing-to-hotwire-infinite-scroll-3273</guid>
      <description>&lt;p&gt;Rails scaffold is a technique for quickly generating a typical CRUD UI. &lt;/p&gt;

&lt;p&gt;It's a server-side rendered html which allows listing/editing/creating/deleting records.&lt;/p&gt;

&lt;p&gt;One of the promises of the modern approach to building UI (like hotwire or stimulus reflex) is how easy it is to just tweak the backend logic, without using JavaScript at all.&lt;/p&gt;

&lt;p&gt;Let's look at the example of infinite scroll - full tutorial how to do it is &lt;a href="https://lewisyoul.github.io/infinitely-scrolling-lists-with-hotwire-and-zero-javascript"&gt;here&lt;/a&gt; and &lt;a href="https://dev.to/stevepolitodesign/create-an-infinite-scrolling-blog-roll-in-rails-with-hotwire-2a8n"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I just want to focus on the "diff" how to make it work.&lt;/p&gt;

&lt;p&gt;This is a typical Rails scaffold preparing all the posts to be displayed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the equivalent Rails view (rendering html).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, in order to use Hotwire (actually Turbo Frame) we change the controller to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;PER_PAGE&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PER_PAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;PER_PAGE&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short, we pass 3 parameters to the view: page, posts, next_page.&lt;/p&gt;

&lt;p&gt;And the view changes to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"posts_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@next_page&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"posts_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@next_page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;loading: :lazy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;posts_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;page: &lt;/span&gt;&lt;span class="vi"&gt;@next_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We wrap the whole thing with a &lt;code&gt;turbo_frame_tag&lt;/code&gt; and we append more such frames for next pages. That's it.&lt;/p&gt;

&lt;p&gt;As you can see the middle of the view stayed the same. &lt;/p&gt;

&lt;p&gt;The UI now lists posts and keeps appending them when we scroll down.&lt;/p&gt;

&lt;p&gt;I'm not claiming that it's amazing or something. But the practicality of getting our app more interactive, while not jumping to JavaScript is simple yet powerful.&lt;/p&gt;

&lt;p&gt;I like it as it allows me to gradually extend my app with new features. &lt;/p&gt;

&lt;p&gt;If you like audio to educate yourself then I recommend this podcast to learn more how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codewithjason.com/rails-with-jason-podcast/episodes/092-vladimir-dementyev-5fK__ZQf/"&gt;https://www.codewithjason.com/rails-with-jason-podcast/episodes/092-vladimir-dementyev-5fK__ZQf/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>5days5blogposts</category>
      <category>rails</category>
      <category>programming</category>
    </item>
    <item>
      <title>E-commerce modules worth extracting in the code</title>
      <dc:creator>Andrzej Krzywda</dc:creator>
      <pubDate>Wed, 21 Apr 2021 11:58:36 +0000</pubDate>
      <link>https://dev.to/andrzejkrzywda/e-commerce-modules-worth-extracting-in-the-code-1a57</link>
      <guid>https://dev.to/andrzejkrzywda/e-commerce-modules-worth-extracting-in-the-code-1a57</guid>
      <description>&lt;p&gt;When you work on a non-trivial e-commerce application the complexity is usually, well non-trivial too.&lt;/p&gt;

&lt;p&gt;E-commerce is one of this kind of apps, where just following a CRUD approach may not be enough.&lt;/p&gt;

&lt;p&gt;Here is a list of modules (definitely not a full list) that might be worth having as separate, in order to avoid coupling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ordering
&lt;/h2&gt;

&lt;p&gt;This is a classic one. If you have no modules in your e-commerce code then you can call it Ordering anyway, because that's the most important. &lt;br&gt;
Some possible operations (commands) here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initiate an order (usually with some items from the cart)&lt;/li&gt;
&lt;li&gt;confirm the order (so that other modules are aware)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ecommerce
&lt;/h2&gt;

&lt;p&gt;I recently like to call this Ecommerce - a module which contains the logic around the cart concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catalog
&lt;/h2&gt;

&lt;p&gt;This module allows adding products to the offer visible to the clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventory
&lt;/h2&gt;

&lt;p&gt;Keep track of how many items are left. Sometimes booking too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pricing is usually not as trivial as just assigning a price to one product. USually we have a whole range of pricing strategies that change over time. It's nice to encapsulate them and keep it separate from other modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payments
&lt;/h2&gt;

&lt;p&gt;Keep track of what was paid for. Sometimes your order will be paid in multiple payments, sometimes it's not fully paid. Sometimes you pay for multiple orders with one payment - hadle this logic here.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sample application with ecommerce modules
&lt;/h1&gt;

&lt;p&gt;I'm working on sample application which shows some of those concepts. It started as a ordering system, but now it's in a process of becoming an e-commerce app too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/RailsEventStore/cqrs-es-sample-with-res"&gt;https://github.com/RailsEventStore/cqrs-es-sample-with-res&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Are there any other modules that you like to extract in ecommerce apps? Share them in the comments :)&lt;/p&gt;

</description>
      <category>programming</category>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
