<?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: Marley Spoon</title>
    <description>The latest articles on DEV Community by Marley Spoon (@marleyspoon).</description>
    <link>https://dev.to/marleyspoon</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%2Forganization%2Fprofile_image%2F3664%2Fe4d98e46-f4f6-4059-8646-fbecf65f8350.png</url>
      <title>DEV Community: Marley Spoon</title>
      <link>https://dev.to/marleyspoon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marleyspoon"/>
    <language>en</language>
    <item>
      <title>The Four Principles of Design Thinking in Software Architecture</title>
      <dc:creator>João Hélio</dc:creator>
      <pubDate>Wed, 12 Feb 2025 09:20:42 +0000</pubDate>
      <link>https://dev.to/marleyspoon/the-four-principles-of-design-thinking-in-software-architecture-o8d</link>
      <guid>https://dev.to/marleyspoon/the-four-principles-of-design-thinking-in-software-architecture-o8d</guid>
      <description>&lt;p&gt;Design Thinking is less a process and more a way to think about problems and solutions from the people's perspective and how the architecture affects the stakeholders.&lt;/p&gt;

&lt;p&gt;In this post, I’m going to highlight the four principles of Design Thinking and how they can be applied to software development to guide our design activities.&lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Design for Humans&lt;/strong&gt;&lt;br&gt;
We design software for people and with people, in the end, &lt;strong&gt;software development is an intensely social and collaborative activity&lt;/strong&gt;. The idea to &lt;em&gt;create a software&lt;/em&gt; architecture &lt;em&gt;isolated&lt;/em&gt; from the team &lt;em&gt;is a myth&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As we design a system, we work closely with our team, fostering mutual respect by actively listening, assuming positive intent, and embracing human-centered design principles.&lt;/p&gt;

&lt;p&gt;Empathizing with the humans who directly and indirectly interact with the architecture makes us a better designer, communicators, and leaders.&lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Preserve Ambiguity&lt;/strong&gt;&lt;br&gt;
Ambiguity in engineering is dangerous. Once we've made a design decision, we must share it with precision and clarity. However, &lt;strong&gt;design decisions that do not directly influence a quality attribute or reduce risk threatening our ability to deliver software are more about detailed design than architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Such decisions can safely be left open for downstream designers to settle outside the architecture. &lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Design is Redesign&lt;/strong&gt;&lt;br&gt;
The redesign rule encourages us to think about what we already know by exploring patterns and past designs. As time goes on and as we build more software, our institutional knowledge about how to design great software improves. Other teams have probably seen a problem similar to the one you are facing currently. Hopefully, someone documented a pattern you can use as a starting point for your architecture.&lt;/p&gt;

&lt;p&gt;When designing software architectures, &lt;strong&gt;we'll spend more time refining existing designs than creating new ones&lt;/strong&gt;. One of the least effective ways to design an architecture is to ignore the software system that came before us.&lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Make the Architecture Tangible&lt;/strong&gt;&lt;br&gt;
While the structures in the architecture can exist in code, this does not make the architecture any more tangible.&lt;/p&gt;

&lt;p&gt;Code is difficult to read and does not make discussions about quality attributes, coarse-grained components, design rationale, or the consequences of our decisions any easier.&lt;/p&gt;

&lt;p&gt;If we want to share an architecture with others, then we need to make it real in a way that code by itself will not allow.&lt;/p&gt;

&lt;p&gt;The tangible rule is closely related to the human rule. Humans &lt;strong&gt;must be able to relate to ideas to internalize them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Design It&lt;/strong&gt;: From Programmer to Software Architect by Michael Keeling&lt;/p&gt;

&lt;p&gt;What are your thoughts? Let’s discuss it! 👇  &lt;/p&gt;

</description>
      <category>designthinking</category>
    </item>
    <item>
      <title>Thinking in Abstractions</title>
      <dc:creator>João Hélio</dc:creator>
      <pubDate>Wed, 09 Oct 2024 09:57:49 +0000</pubDate>
      <link>https://dev.to/marleyspoon/thinking-in-abstractions-4hde</link>
      <guid>https://dev.to/marleyspoon/thinking-in-abstractions-4hde</guid>
      <description>&lt;p&gt;A quick fix in the code can solve many issues, but it may create complexity that becomes difficult to maintain over time. This happens as more and more rules are added to address specific problems within a general context. Let's look at a use case and explore some potential solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;p&gt;Imagine you have a general &lt;code&gt;Order&lt;/code&gt; class, but now the business requires handling two specific cases. The &lt;strong&gt;Halloween Order&lt;/strong&gt; must contain at least ten line items, while the &lt;strong&gt;Christmas Order&lt;/strong&gt; can be empty but must include at least one add-on.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Try: &lt;code&gt;IF-ELSE&lt;/code&gt; Statement
&lt;/h3&gt;

&lt;p&gt;We can attempt to handle this with conditionals like the following:&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;Order&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:add_ons&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Basic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Standard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="vi"&gt;@line_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line_items&lt;/span&gt;
    &lt;span class="vi"&gt;@plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;
    &lt;span class="vi"&gt;@type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;
    &lt;span class="vi"&gt;@add_ons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;validate&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Standard'&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Total amount must be greater least one line item'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@line_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Halloween'&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Halloween order must have at least ten line items'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@line_items&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Christmas'&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Christmas order must have at least one add-on'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@add_ons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&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 plaintext"&gt;&lt;code&gt;# Example usage
halloween_order = Order.new([], 'Premium', 'Halloween')
halloween_order.validate
=&amp;gt; `validate': Halloween order must have at least ten line items (RuntimeError)

christmas_order = Order.new([], 'Enterprise', 'Christmas')
christmas_order.validate
=&amp;gt; `validate': Total amount must be greater least one line item (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity in Small-Scale Scenarios&lt;/strong&gt;: using &lt;code&gt;if-else&lt;/code&gt; statements can be the quickest way to implement logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer Lines of Code&lt;/strong&gt;: For simple conditional logic may result in fewer lines of code at first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Need for Overhead in Basic Use Cases&lt;/strong&gt;: In situations where we don’t expect many changes or extensions to the logic, &lt;code&gt;if-else&lt;/code&gt; can avoid unnecessary overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mixed Responsibilities&lt;/strong&gt;: The &lt;code&gt;Order&lt;/code&gt; class is now responsible for both general order logic and the specific rules for Halloween and Christmas orders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficult to Extend&lt;/strong&gt;: Adding a new order type means modifying the core &lt;code&gt;Order&lt;/code&gt; class, which introduces risk and reduces flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harder to Test&lt;/strong&gt;: We can end up having a test that is hard to understand when specific rules are incorporated.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Second try: Abstraction Through Inheritance
&lt;/h3&gt;

&lt;p&gt;We can create &lt;strong&gt;specialized classes&lt;/strong&gt; that capture the architectural intent. By abstracting common behaviour into a base class and allowing each type to manage its own rules.&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;Order&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:add_ons&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Basic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="vi"&gt;@line_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line_items&lt;/span&gt;
    &lt;span class="vi"&gt;@plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;
    &lt;span class="vi"&gt;@add_ons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;validate&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Total amount must be greater least one line item'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@line_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&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;class&lt;/span&gt; &lt;span class="nc"&gt;HalloweenOrder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Order&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Basic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;validate&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Halloween order must have at least ten line items'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@line_items&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&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;class&lt;/span&gt; &lt;span class="nc"&gt;ChristmasOrder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Order&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Basic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;line_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_ons&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;validate&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Christmas order must have at least one add-on'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@add_ons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&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 plaintext"&gt;&lt;code&gt;# Example usage
halloween_order = HalloweenOrder.new([], 'Premium')
halloween_order.validate                                 
=&amp;gt; `validate': Halloween order must have at least ten line items (RuntimeError)

christmas_order = ChristmasOrder.new([], 'Enterprise')
christmas_order.validate
=&amp;gt; `validate': Christmas order must have at least one add-on (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns&lt;/strong&gt;: The base &lt;code&gt;Order&lt;/code&gt; class only handles shared behaviour. The specific rules for &lt;code&gt;HalloweenOrder&lt;/code&gt; and &lt;code&gt;ChristmasOrder&lt;/code&gt; are handled in their respective classes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Adding a new order type (e.g., &lt;code&gt;EasterOrder&lt;/code&gt;, &lt;code&gt;BlackFridayOrder&lt;/code&gt;) is easy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to Test&lt;/strong&gt;: Testing new use cases doesn't require a shared setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Overhead for Simple Use Cases&lt;/strong&gt;: When the use case is simple, setting up multiple classes and abstractions can introduce unnecessary complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-Engineering&lt;/strong&gt;: YAGNI ("You Aren’t Gonna Need It") we might introduce abstractions for future possibilities that never materialize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More Classes to Maintain&lt;/strong&gt;: Abstracting logic into specialized classes can increase the number of files and classes, which means more "moving parts". &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The patch and the module abstraction methods have their place in software design. The key is to assess the complexity and evolution of the system.&lt;/p&gt;

&lt;p&gt;Start simple and refactor when complexity arises. This balance ensures the architecture remains maintainable and adaptable without becoming overly complicated.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Retro Guide</title>
      <dc:creator>João Hélio</dc:creator>
      <pubDate>Mon, 20 Nov 2023 09:20:37 +0000</pubDate>
      <link>https://dev.to/marleyspoon/retro-guide-2l6p</link>
      <guid>https://dev.to/marleyspoon/retro-guide-2l6p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The sprint retrospective is a moment for our team to reflect on the past sprint, celebrate successes, and identify areas for improvement. By fostering a culture of continuous learning, we aim to enhance our work process and achieve even greater success in the future. To achieve this, strategic planning is essential, ensuring that the retrospective becomes a well-orchestrated effort toward sustained improvement and growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Directives
&lt;/h2&gt;

&lt;p&gt;At the heart of our retrospective is the understanding that everyone did their best given the circumstances. We value cooperation, recognizing that our unique strengths are amplified when we work together. Hope and trust emerge through engagement, and we seize this opportunity to unite around a shared vision for the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Gaining insight into the project context provides a clear understanding of our position in the project, making it particularly valuable for the sprint retrospective. If time constraints prevent context discussion during other events like sprint review or planning, consider incorporating it as an introduction to the retrospective.&lt;/p&gt;

&lt;p&gt;Here are some aspects of the context that might be relevant to a retrospective:&lt;/p&gt;

&lt;h4&gt;
  
  
  Start or End of a Project
&lt;/h4&gt;

&lt;p&gt;Whether the retrospective marks the beginning or end of a project, the context would include specific project goals, team changes, initial expectations, and achievements.&lt;/p&gt;

&lt;h4&gt;
  
  
  Team Changes
&lt;/h4&gt;

&lt;p&gt;If there have been significant changes to the team composition (members joining or leaving), the context may include how these changes impacted team dynamics and performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementation of New Processes
&lt;/h4&gt;

&lt;p&gt;If the team has recently adopted new processes or tools, context can address the effectiveness of these changes and how they have influenced workflow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Development Cycle
&lt;/h4&gt;

&lt;p&gt;Timing in the development cycle iteration (such as a specific sprint) is essential. Discussions can focus on goals met, user stories delivered, and obstacles faced during this sprint.&lt;/p&gt;

&lt;h4&gt;
  
  
  External Events
&lt;/h4&gt;

&lt;p&gt;External events, such as changes in customer demands, industry updates, or unexpected challenges, provide crucial context for understanding how the team responded to these external factors.&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance Metrics
&lt;/h4&gt;

&lt;p&gt;Quantitative metrics such as team velocity, user story completion rate, or code quality can be part of the context, providing an objective basis for analysis.&lt;/p&gt;

&lt;h4&gt;
  
  
  Customer Feedback
&lt;/h4&gt;

&lt;p&gt;If there was customer feedback during or after the sprint, that feedback becomes part of the context, helping the team understand how their deliverables are perceived.&lt;/p&gt;

&lt;h4&gt;
  
  
  Organizational Objectives
&lt;/h4&gt;

&lt;p&gt;Contextualizing the retrospective about the organization's broader goals helps align the team's priorities with the company's overall goals.&lt;/p&gt;

&lt;h4&gt;
  
  
  Evaluation of Training or Culture Changes
&lt;/h4&gt;

&lt;p&gt;If the team participated in training or if the organization is going through cultural changes, this context is crucial to evaluate how these initiatives are being absorbed and applied.&lt;/p&gt;

&lt;h4&gt;
  
  
  Focus on the Current Sprint
&lt;/h4&gt;

&lt;p&gt;Direct the discussion to the most recent sprint, ensuring that observations and learnings are specific to the team's current situation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Specific Goal Orientation
&lt;/h4&gt;

&lt;p&gt;This allows a team to focus on specific areas that need attention and improvement.&lt;/p&gt;

&lt;h4&gt;
  
  
  Connection to Long-Term Goals
&lt;/h4&gt;

&lt;p&gt;The retrospective should be seen as part of a continuous improvement process. Timing/context helps link the discussion to the long-term goals of the team and organization.&lt;/p&gt;

&lt;h4&gt;
  
  
  Identification of Trends Over Time
&lt;/h4&gt;

&lt;p&gt;You can identify trends over multiple sprints, which can lead to relevant insights into performance patterns and consistent areas for improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Participant Preparation
&lt;/h2&gt;

&lt;p&gt;For our retrospective to be effective, the team members are encouraged to reflect on specific questions before the meeting to facilitate a more insightful discussion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked well:&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Standout moments in the sprint.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "The continued refinement of the Rewards topic was a highlight, allowing us to define the goals of the domain better."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Effective practices or processes.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Adopting the clean architecture concepts has helped us to create a more scalable system."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Exceptional collaboration between team members.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Close collaboration between developers and PMs during the Q&amp;amp;A phase facilitated rapid identification and resolution of bugs."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Individual skills that contributed to success.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "John's problem-solving skills were instrumental in overcoming unexpected challenges during development."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Aspects of the work that led to overall success.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Clarity in user stories helped avoid misunderstandings and allowed smoother implementation."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Positive changes compared to the previous sprint.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Implementing &lt;code&gt;excellent_migrations&lt;/code&gt; tool reduced the bugs to zero when creating a new migration."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Each team member's main contributions.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Maria effectively led the resolution of a critical problem, demonstrating her ability to deal with challenging situations."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What can be improved:&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Main obstacles or challenges faced.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Integration with the legacy API system was more complicated than expected, causing significant delays."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Communication difficulties negatively impact the team.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "The lack of regular updates on task progress has confused me about priorities. When we say 'I'm working on it and don't have any blockers' on Dailies meeting, we lose the opportunity to receive feedback and share knowledge."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Aspects of the work process that can be optimized.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Reviewing milestones and deadlines regularly can give us evidence to adapt to the current tasks."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Recurring issues that need addressing.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Dependence on a single specialist for certain critical tasks has caused bottlenecks. We must diversify knowledge within the team."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Gaps in the team’s skills or knowledge.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "The lack of expertise in a new technology impacted the efficient implementation of a feature. We must seek training."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Handling external pressures or tight deadlines.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Deadline pressure has affected code quality. We should explore strategies to maintain an appropriate balance between speed and quality."&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Tasks or activities that did not contribute significantly.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Example: "Allocating extensive resources to infrequently requested functionality may have been unnecessary. We must evaluate the value before initiating such tasks."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checkin
&lt;/h2&gt;

&lt;p&gt;Collect the participants' sentiments. This sets the tone for a focused and constructive discussion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Improvement
&lt;/h2&gt;

&lt;p&gt;Engage in data collection, emotional expression, and acknowledgment of positive aspects. Explore opportunities for enhancement to commit to continuous improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering
&lt;/h2&gt;

&lt;p&gt;Prioritize discussion topics to generate insights within the available time efficiently. Streamline the conversation to focus on the most crucial areas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkout
&lt;/h2&gt;

&lt;p&gt;As the meeting concludes, assign responsibilities for action items identified during the retrospective. Clearly define who will be responsible for each task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In summary, the retrospective process is designed to promote a culture of continuous improvement within our team. By encouraging thoughtful participant preparation, considering relevant context, and fostering open communication, we aim to glean valuable insights for enhancing our work processes. Through these efforts, we aspire to create a collaborative environment that consistently strives for excellence.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.funretrospectives.com"&gt;Fun Retrospectives&lt;/a&gt;: A diverse collection of activities and techniques for engaging retrospectives.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;The Retrospective Handbook - A guide for agile teams&lt;/em&gt; by Patrick Kua: A practical guide for agile teams running effective retrospectives.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Agile Retrospectives - Making Good Teams Great&lt;/em&gt; by Esther Derby, Diana Larsen, Ken Schwaber: A comprehensive overview of conducting effective agile retrospectives.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agile</category>
    </item>
    <item>
      <title>The Request and Response Model in the Clean Architecture</title>
      <dc:creator>João Hélio</dc:creator>
      <pubDate>Thu, 31 Aug 2023 10:00:27 +0000</pubDate>
      <link>https://dev.to/marleyspoon/the-request-and-response-model-in-the-clean-architecture-517c</link>
      <guid>https://dev.to/marleyspoon/the-request-and-response-model-in-the-clean-architecture-517c</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;One of the key concepts within the Clean Architecture is the Request and Response model, which helps to separate the core application logic from external concerns such as user interfaces and frameworks. In this article, we'll explore the Request and Response model and demonstrate its implementation using Ruby.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Request and Response Model
&lt;/h3&gt;

&lt;p&gt;The Request and Response model is a pattern that promotes a clear separation between the inputs and outputs of the application. By doing so, the architecture becomes more adaptable to changes and easier to test, as the core logic is not tightly coupled to any specific framework or external component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;

&lt;p&gt;A request in the Clean Architecture represents the input to a use case or an operation within the application. It encapsulates the necessary data required for the operation to be executed. Requests are usually simple data structures and do not contain any business logic. They act as a boundary between the external world (such as UI) and the application's core.&lt;/p&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;

&lt;p&gt;On the other hand, a response represents the output of a use case or operation. It contains the result of the operation, including any data that needs to be presented to the user or propagated to other parts of the application. Similar to requests, responses are also typically simple data structures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing
&lt;/h3&gt;

&lt;p&gt;Let's consider a simple example of a blog application. We'll implement the Request and Response model for a use case where a user wants to create a new blog post.&lt;/p&gt;

&lt;p&gt;In the following example, we define &lt;code&gt;PostRequest&lt;/code&gt; as a &lt;code&gt;Dry::Validation::Contract&lt;/code&gt; and &lt;code&gt;PostResponse&lt;/code&gt; using &lt;code&gt;Dry::Struct&lt;/code&gt;. The &lt;code&gt;CreatePost&lt;/code&gt; use case takes a request object, processes the business logic, and returns a response object.&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry/struct'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry/validation'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Blog&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;UseCases&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Posts&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostRequest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:author_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:integer&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;class&lt;/span&gt; &lt;span class="nc"&gt;PostResponse&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Struct&lt;/span&gt;
        &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&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;Bool&lt;/span&gt;
        &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&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="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&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;Integer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&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;CreatePost&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;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;validation_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PostRequest&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="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validation_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;
            &lt;span class="c1"&gt;# Business logic to create a new post&lt;/span&gt;
            &lt;span class="c1"&gt;# ...&lt;/span&gt;

            &lt;span class="c1"&gt;# Return a response&lt;/span&gt;
            &lt;span class="no"&gt;PostResponse&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;success: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s1"&gt;'Post created successfully'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="c1"&gt;# Return a response with validation errors&lt;/span&gt;
            &lt;span class="no"&gt;PostResponse&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;success: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s1"&gt;'Validation errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="kp"&gt;nil&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;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;span class="c1"&gt;# Example usage&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UseCases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PostRequest&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;title: &lt;/span&gt;&lt;span class="s1"&gt;'Sample Post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s1"&gt;'This is the content of the post.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;author_id: &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;use_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UseCases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreatePost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;use_case&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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see how we can write a test for the &lt;code&gt;CreatePost&lt;/code&gt; use case:&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="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UseCases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreatePost&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:use_case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;described_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="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'when the request is valid'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:valid_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UseCases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreatePostRequest&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;title: &lt;/span&gt;&lt;span class="s1"&gt;'Sample Post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s1"&gt;'This is the content of the post.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;author_id: &lt;/span&gt;&lt;span class="mi"&gt;123&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;it&lt;/span&gt; &lt;span class="s1"&gt;'creates a new post'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;use_case&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="n"&gt;valid_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Post created successfully'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your expected post ID&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'when the request is invalid'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:invalid_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UseCases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreatePostRequest&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;title: &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s1"&gt;'This is the content of the post.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;author_id: &lt;/span&gt;&lt;span class="kp"&gt;nil&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;it&lt;/span&gt; &lt;span class="s1"&gt;'returns validation errors'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;use_case&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="n"&gt;invalid_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Validation errors'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_nil&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;To explore the full capabilities of the dry-rb ecosystem check the &lt;a href="https://dry-rb.org/"&gt;website&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of the Request and Response Model
&lt;/h3&gt;

&lt;p&gt;By separating requests and responses, we achieve a clear boundary between external concerns and core business logic and since requests and responses are simple data structures, testing becomes straightforward, and we can easily write unit tests for each use case.&lt;/p&gt;

&lt;p&gt;The Clean Architecture, coupled with the Request and Response model, allows us to change external components or frameworks without affecting the core logic.&lt;/p&gt;

&lt;p&gt;In conclusion, the Request and Response model is a powerful concept within the Clean Architecture that promotes separation of concerns and maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;"Clean Architecture: A Craftsman's Guide to Software Structure and Design" by Robert C. Martin&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Understanding Bounded Contexts in Ruby</title>
      <dc:creator>João Hélio</dc:creator>
      <pubDate>Thu, 17 Aug 2023 09:33:28 +0000</pubDate>
      <link>https://dev.to/marleyspoon/understanding-bounded-contexts-in-ruby-13el</link>
      <guid>https://dev.to/marleyspoon/understanding-bounded-contexts-in-ruby-13el</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Bounded contexts have a crucial role in managing complexity, maintaining a clear separation of concerns, and ensuring that different parts of a system can communicate effectively without causing conflicts or confusion. Bounded contexts are a fundamental concept in Domain-Driven Design (DDD) and are particularly relevant when building complex systems. &lt;/p&gt;

&lt;p&gt;In this article, the bounded contexts will be explored using a practical example in Ruby, where we'll demonstrate how to consume and wrap a Plan object between two different contexts: the Plan Management context and the Sales context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Bounded Contexts
&lt;/h2&gt;

&lt;p&gt;A bounded context is a conceptual boundary within which a particular domain model is defined and applicable. Different parts of a software system may have different understandings of specific terms, rules, and behaviors. Bounded contexts help avoid ambiguity and conflicts by isolating other models within their own context, making sure they remain consistent and meaningful within that context.&lt;/p&gt;

&lt;p&gt;In our example scenario, imagine a software application for a company that offers subscription plans to its customers. There are two primary bounded contexts: Plan Management and Sales.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Plan Management Context: This context is responsible for creating, updating, and managing subscription plans. It focuses on the administrative aspects of plan creation and modification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sales Context: This context is responsible for handling customer interactions, including the sale of subscription plans. It deals with pricing, availability, and customer-specific details.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tXiUwXTM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61f3pausexdgczb90okk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tXiUwXTM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61f3pausexdgczb90okk.png" alt="Image description" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming and Wrapping Plan Objects
&lt;/h2&gt;

&lt;p&gt;Let's dive into the example scenario to understand how bounded contexts work in Ruby. We'll begin by creating a simplified Plan class that holds basic plan information. Then, we'll demonstrate how to consume and wrap a Plan object between the two contexts.&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="c1"&gt;# Plan class in the Plan Management context&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;PlanManagement&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Plan&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:id&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;:price&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&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="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
      &lt;span class="vi"&gt;@price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&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;update_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_price&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="c1"&gt;# Plan class in the Sales context&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sales&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Plan&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:plan_dto&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;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan_dto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;
      &lt;span class="vi"&gt;@plan_dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plan_dto&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;calculate_discount&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;plan_dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;name&lt;/span&gt;
      &lt;span class="n"&gt;plan_dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&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;price&lt;/span&gt;
      &lt;span class="n"&gt;plan_dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credit&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;In this example, we have two classes defined in different contexts, the Plan class in the Plan Management context and the Plan in the Sales context. Each class is tailored to its respective context's needs.&lt;/p&gt;

&lt;p&gt;To maintain the separation between the contexts and ensure smooth communication, we need to wrap Plan objects when transitioning between contexts. Let's illustrate how this can be done:&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="c1"&gt;# Plan Management context&lt;/span&gt;
&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PlanManagement&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plan&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Basic Plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sales context&lt;/span&gt;
&lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Customer&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="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sales_plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sales&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plan&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;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage in the Sales context&lt;/span&gt;
&lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sales_plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate_discount&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Customer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gets a discount of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% on the &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sales_plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; plan."&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Plan details:"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sales_plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Price: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sales_plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;Sales::Plan&lt;/code&gt; class provides a bridge between the Sales context and the Plan Management context. It wraps the Plan object from the Plan Management context and exposes only the methods relevant to the Sales context, such as calculating discounts based on a customer's profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By defining boundaries around different domain models and using wrapper classes when transitioning between contexts, we can maintain consistency, reduce conflicts, and promote a better understanding of how other parts of the system interact. In our Ruby example, we've demonstrated how bounded contexts can be applied to a Plan object, showcasing the practical benefits of this concept in real-world software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans This book is often considered the authoritative source on Domain-Driven Design (DDD) and introduces the concept of bounded contexts along with various other DDD principles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bounded Context - Martin Fowler's blog is a valuable resource for software design patterns and concepts. His article on bounded contexts provides a comprehensive explanation with examples.&lt;br&gt;
Link: &lt;a href="https://martinfowler.com/bliki/BoundedContext.html"&gt;https://martinfowler.com/bliki/BoundedContext.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>boundedcontext</category>
      <category>ddd</category>
    </item>
    <item>
      <title>Taming Time: how to run XTDB in production</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Wed, 19 Jul 2023 11:13:57 +0000</pubDate>
      <link>https://dev.to/marleyspoon/taming-time-how-to-run-xtdb-in-production-2lgd</link>
      <guid>https://dev.to/marleyspoon/taming-time-how-to-run-xtdb-in-production-2lgd</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/ne1ro/series/22497"&gt;In the previous articles&lt;/a&gt;, we explored the concept of bitemporality and discussed how to get started with XTDB, a bitemporal immutable database. Now, let’s dive into the technical details of deploying XTDB and running it in production. This blog post aims to provide valuable insights and considerations to keep in mind during this process.&lt;/p&gt;

&lt;h1&gt;
  
  
  XTDB 1
&lt;/h1&gt;

&lt;p&gt;Before we proceed, it’s important to note that the experiences shared in this article are based on working with XTDB version 1.21.&lt;br&gt;
It’s worth mentioning that your experience may vary, especially with the introduction of &lt;a href="https://www.xtdb.com/v2"&gt;XTDB 2.0&lt;/a&gt; and subsequent versions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploying XTDB
&lt;/h1&gt;

&lt;p&gt;Deploying XTDB in a production environment offers several options, each with its advantages and considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  run as a part of your JVM application&lt;/li&gt;
&lt;li&gt;  run separately, but on the same server together with your application&lt;/li&gt;
&lt;li&gt;  run on a standalone node or in a separate container&lt;/li&gt;
&lt;li&gt;  run a cluster of XTDB nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve a resilient and scalable setup, I recommend running a cluster of XTDB nodes, with each node deployed as a separate Docker container managed by Kubernetes. This allows easy orchestration, automatic scaling, and simplified management of the XTDB cluster.&lt;/p&gt;
&lt;h2&gt;
  
  
  Containerised XTDB
&lt;/h2&gt;

&lt;p&gt;In this section, we will explore how to build and run a Docker container with a custom configured XTDB application &lt;em&gt;(Clojure project)&lt;/em&gt; in a Kubernetes cluster. However, in case you don’t need to have a custom build and can simply use a &lt;a href="https://hub.docker.com/r/juxt/xtdb-standalone-rocksdb"&gt;standalone Docker image&lt;/a&gt;, you can skip right to the "Authentication" section.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uberjar
&lt;/h3&gt;

&lt;p&gt;If we want to run the XTDB in Docker the most fitting way to run it in a container would be to compile our preconfigured XT application into a single “uberjar” file.&lt;/p&gt;

&lt;p&gt;This can be done by employing &lt;a href="https://github.com/tonsky/uberdeps"&gt;Uberdeps&lt;/a&gt; in our Clojure project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="no"&gt;:aliases&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:uberdeps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:replace-deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uberdeps/uberdeps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="no"&gt;:replace-paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="no"&gt;:main-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-m"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"uberdeps.uberjar"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have it installed, you can compile your project into a single Uberjar file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clj -M:uberdeps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;We want our XTDB Docker image to be as lightweight as possible, so the best approach would be to have a multi-stage build image that builds and runs the Uberjar on the selected JVM image:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;    &lt;span class="c"&gt;# Build clojure uberjar&lt;/span&gt;
    FROM clojure:openjdk-17-tools-deps-alpine AS BUILD

    WORKDIR /xtdb
    COPY . /xtdb
    RUN apk add --no-cache libstdc++

    RUN clojure -M:uberdeps

    &lt;span class="c"&gt;# Copy and run uberjar&lt;/span&gt;
    FROM openjdk:17-alpine3.14
    WORKDIR /usr/local/lib/xtdb

    RUN apk --no-cache add bash libstdc++

    COPY --from=BUILD /xtdb/resources /usr/local/lib/xtdb/resources
    COPY --from=BUILD /xtdb/target .

    ENV MALLOC_ARENA_MAX=2
    CMD ["java", "-cp", "xtdb.jar", "clojure.main", "-m", "xtdb.core"]

    EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Graceful shutdown
&lt;/h3&gt;

&lt;p&gt;Another requirement for running XT in Kubernetes is to gracefully shut down, e.g., on killing containers or Kubernetes deployment restart. To ensure that, we have to change our &lt;code&gt;xtdb.clj&lt;/code&gt; file by adding a &lt;code&gt;SIGTERM&lt;/code&gt; signal handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;    ;; Stop the system on SIGTERM
    (with-handler :term
      (log/info "Caught SIGTERM, quitting")
      (.close @xt-node)
      (log/info "All components shut down")
      (System/exit 0))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Since we run our XTDB separately from our application containers, we might want to ensure that the requests from services to the database are properly authenticated.&lt;/p&gt;

&lt;p&gt;In order to ensure that we need to change the way we start XTDB by providing JWKS &lt;a href="https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets"&gt;(JSON Web Token key set)&lt;/a&gt; as an environment variable to the XTDB node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; core.clj&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.http-server/server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:jwks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"XTDB_JWKS"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to send the compatible JWT token from your application requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;your_jwt&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kubernetes
&lt;/h2&gt;

&lt;p&gt;As we’ve decided to go with the running XT nodes as Docker containers in a Kubernetes cluster, we need to prepare Kubernetes manifests for that purpose.&lt;/p&gt;

&lt;p&gt;The simplest way to achieve that is to create a Kubernetes stateful set of multiple XT containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="s"&gt;---&lt;/span&gt;
    &lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
    &lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb-headless&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$REGISTRY/xtdb:1.21.0&lt;/span&gt;
              &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
              &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
              &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
                &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
                &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
                &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
              &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
                    &lt;span class="c1"&gt;# custom readiness check script&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;scripts/readiness.sh&lt;/span&gt;
                &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
                &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
              &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb-secrets&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As for the configuration, you can create a configmap with environment variables required for configuring XTDB and a secrets resource for providing secrets to your XT nodes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Running XT in production
&lt;/h1&gt;

&lt;p&gt;XTDB is an &lt;em&gt;unbundled database&lt;/em&gt; which means that it has a lot of components that can be swapped or changed — and it might work with different technologies and other databases.&lt;/p&gt;

&lt;p&gt;In general, it consists of 3 parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Transaction log&lt;/li&gt;
&lt;li&gt; Document store&lt;/li&gt;
&lt;li&gt; Index store&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We had an experience using PostgreSQL and JDBC adapter for our XTDB setup, as well as experimenting with Kafka for transaction log — and using RocksDB as an index storage.&lt;/p&gt;

&lt;p&gt;However, there are many more other ways and modules to setup the database — you can find them in &lt;a href="https://docs.xtdb.com/administration/configuring/"&gt;the documentation.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  JDBC
&lt;/h2&gt;

&lt;p&gt;The transaction log and document store are considered to be &lt;strong&gt;golden stores&lt;/strong&gt; in XTDB — which means that they should be reliably persisted, unlike the index storage that can be rebuilt from scratch on node restart.&lt;/p&gt;

&lt;p&gt;XT supports JDBC (Java Database Connectivity) which allows us to connect to various SQL databases like PostgreSQL, MySQL, SQLite, and others.&lt;br&gt;
In our example, we were using PostgreSQL + JDBC for both transaction log and document store.&lt;/p&gt;

&lt;p&gt;To use PostgreSQL and JDBC together, you have to provide these modules in your &lt;em&gt;deps.edn&lt;/em&gt; first:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:deps {org.postgresql/postgresql {:mvn/version "42.2.18"}
        com.xtdb/xtdb-jdbc {:mvn/version "1.21.0"}
       ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you want to use environment variables for connecting to PostgreSQL from the XTDB deployment you can also pass them in &lt;strong&gt;core.clj&lt;/strong&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PORT"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"5432"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:dbname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_DB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_USER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:dialect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc.psql/-&amp;gt;dialect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:pool-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:maximumPoolSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:db-spec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/tx-log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;tx-log&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/document-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;document-store&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way we can also re-use the same connection pool for both transaction and document store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kafka
&lt;/h2&gt;

&lt;p&gt;However, the recommended option (and also most often used in production) is to leverage Kafka for the transaction store.&lt;/p&gt;

&lt;p&gt;During node restarts &lt;em&gt;(e.g. on new deployments)&lt;/em&gt; XTDB has to rebuild the transaction log from zero or the latest saved checkpoint, which means reading the whole transaction table in PostgreSQL — and in our experience, this process was slower than expected.&lt;/p&gt;

&lt;p&gt;Kafka seems to be a better fit for the very purpose of the transaction log because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  it’s basically a log of events&lt;/li&gt;
&lt;li&gt;  we only need to use just one partition and one topic&lt;/li&gt;
&lt;li&gt;  the transactions can be consumed very quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, the most optimal and performant setup for us looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  using Kafka as a transaction log&lt;/li&gt;
&lt;li&gt;  using JDBC and relational database as a document store&lt;/li&gt;
&lt;li&gt;  using RocksDB as an index store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way, we can ensure that the transactions can be created quickly, the log can be re-consumed fast, and the document storage is performant enough and resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkpoints
&lt;/h2&gt;

&lt;p&gt;If we want to rebuild the query indices (e.g. on node restart), XT might need to replay the transaction log — which sometimes might be not so fast especially if you have a long history of changes.&lt;/p&gt;

&lt;p&gt;As we run our XTDB in a cluster, it’s vital that the readiness time — when the XT instance is ready to serve DB requests — is as low as possible.&lt;br&gt;
Fortunately, XTDB has a solution for that problem called &lt;strong&gt;checkpoints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Right now, there are three ways to persist the local query indices state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  local files (using Java’s NIO file system)&lt;/li&gt;
&lt;li&gt;  AWS S3&lt;/li&gt;
&lt;li&gt;  GPC’s cloud storage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  AWS S3
&lt;/h3&gt;

&lt;p&gt;In our case, we’ve decided to go with the AWS setup in order to have a centralised and already configured storage for the checkpoints.&lt;br&gt;
However, it also requires some additional dependencies to be installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-s3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;software.amazon.awssdk/aws-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2.10.91"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional setup in the node configuration is also required.&lt;/p&gt;

&lt;p&gt;As we’ve experienced some requests to S3 taking a long time, we’ve also decided to build a custom AWS S3 HTTP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make-s3-client&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s"&gt;"Increases timeouts for AWS S3 HTTP calls"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Duration/ofSeconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;http-client-builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;NettyNioAsyncHttpClient/builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.connectionAcquisitionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.connectionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;S3AsyncClient/builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.httpClientBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http-client-builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"CHECKPOINT_NAME"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;; Checkpoints are not enabled on a local machine where we don't have the env&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str/blank?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.checkpoint/-&amp;gt;checkpointer&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:store&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.s3.checkpoint/-&amp;gt;cp-store&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:configurator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;S3Configurator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_this&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make-s3-client&lt;/span&gt;&lt;span class="p"&gt;))))}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:Keep-dir-on-close?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:approx-frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Duration/ofHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, XT will persist the current index state to S3 every 2 hours. One might want to adjust S3’s bucket policy so it archives or removes the obsolete checkpoints files.&lt;/p&gt;

&lt;h1&gt;
  
  
  Caveats
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Memory consumption
&lt;/h2&gt;

&lt;p&gt;JVM-based applications tend to consume quite a significant amount of memory budget — in our case, running XT with allowed 4GB of memory wasn’t always enough so we’ve decided to increase the memory limits in Kubernetes up to 8 gigabytes.&lt;/p&gt;

&lt;p&gt;Another consideration we’ve observed is that the vertical scaling works better for an XTDB cluster — unlike the horizontal scaling, where we need to wait until the new node restores from the checkpoints or processes the transactions log.&lt;/p&gt;

&lt;h2&gt;
  
  
  RocksDB tuning
&lt;/h2&gt;

&lt;p&gt;RocksDB is being used by XT as an index store, and as the result, it might consume quite a significant amount of resources.&lt;br&gt;
In order to avoid possible issues with the memory budgeting, it’s recommended to &lt;a href="https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#block-cache-size"&gt;set RocksDB block cache to 1/3 of available memory&lt;/a&gt; which can be done in XTDB configuration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Readiness probes
&lt;/h2&gt;

&lt;p&gt;Depending on your technology stack that you use for XTDB deployment, consuming the transaction log even with the checkpoints feature enabled can take some time — and even though the starting node is able to handle REST API requests, they won’t be processed until the node finishes the consumption.&lt;/p&gt;

&lt;p&gt;To avoid that, you might need to check the difference between the last submitted and last completed transactions, e.g. from a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
    &lt;span class="c"&gt;# scripts/readiness.sh: A script that compares the latest submitted and indexed transactions&lt;/span&gt;

    &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
    &lt;span class="nv"&gt;THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000

    &lt;span class="c"&gt;# Assumes that you have jq and curl installed&lt;/span&gt;
    &lt;span class="nv"&gt;submitted_tx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;curl http://localhost:3000/_xtdb/latest-submitted-tx &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; | jq .txId&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nv"&gt;completed_tx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;curl http://localhost:3000/_xtdb/latest-completed-tx &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; | jq .txId&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nv"&gt;diff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$[&lt;/span&gt;submitted_tx - completed_tx]

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;diff &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; THRESHOLD&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Node is not ready"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Node is ready"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load balancing and XT cluster
&lt;/h2&gt;

&lt;p&gt;The index storage is not shared between XTDB nodes — so every node might have a slightly different data representation. To ensure integrity, we might need to use &lt;em&gt;await-tx&lt;/em&gt; or &lt;em&gt;sync&lt;/em&gt; functions whenever we submit a transaction.&lt;/p&gt;

&lt;p&gt;However, when we use REST API in a distributed cluster of nodes, it might be that the load balancer that stands in front of the nodes distributes requests to the database randomly — and when we submit a transaction to one node, we can end up reading data from another, which might have not processed that transaction yet.&lt;/p&gt;

&lt;p&gt;If we want to prevent such situation, we might need to implement sticky sessions or use &lt;a href="https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf#http2"&gt;HTTP2&lt;/a&gt; connections between your applications and database nodes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;XTDB embraces the bitemporality concept and provides powerful capabilities of handling your data in an immutable way.&lt;/p&gt;

&lt;p&gt;However, this also implies that during your journey with XT, you might face some technical challenges caused by its unbundled database concept — and resolve them by reasoning about the selected components, technology stack, and implications.&lt;/p&gt;

&lt;p&gt;The new milestone in XTDB’s development — &lt;a href="https://www.xtdb.com/v2"&gt;XTDB 2.0&lt;/a&gt; — looks very promising for us as it has a more flexible and scalable architecture as well as the first-class SQL support — and can be used by PostgreSQL clients.&lt;/p&gt;

&lt;p&gt;We look forward to trying out the new version and hope that you’ve enjoyed our series of articles about XT.&lt;/p&gt;

&lt;p&gt;Happy hacking, and stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; and &lt;a href="https://dev.to/adamniedzielski"&gt;Adam&lt;/a&gt; for the reviews!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>database</category>
    </item>
    <item>
      <title>Don't call it `*_id`!</title>
      <dc:creator>Carsten Zimmermann</dc:creator>
      <pubDate>Sun, 07 May 2023 10:08:22 +0000</pubDate>
      <link>https://dev.to/marleyspoon/dont-call-it-id-5fbf</link>
      <guid>https://dev.to/marleyspoon/dont-call-it-id-5fbf</guid>
      <description>&lt;p&gt;In a distributed system, we often need to store data that we do not own. We might use it as a unique identifier across domains or the system we &lt;em&gt;do&lt;/em&gt; own needs to proxy it to yet another service.&lt;/p&gt;

&lt;p&gt;In many cases, the origin system exposes it in the same way it is represented internally, for instance: a foreign key name of a relational database:&lt;/p&gt;
Fig. 1: local relational table, rev. 1


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+--------------+----------------------+
| id | name         | backend              |
+----+--------------+----------------------+
| 42 | Marley Spoon | elixir, ruby, python |
+----+--------------+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Translated to JSON, we could send it over the wire as follows:&lt;/p&gt;
Fig. 2: example JSON payload


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marley Spoon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"backend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"elixir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;A service consuming the information may want to add prefixes to scope it for "companies" and store/cache it like so:&lt;/p&gt;
Fig. 3: local relational table, rev. 2


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------+--------------+----------------------+
| company_id | company_name | backend              |
+------------+--------------+----------------------+
| 42         | Marley Spoon | elixir, ruby, python |
+------------+--------------+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;But here's the problem: as engineers, we look at &lt;code&gt;_id&lt;/code&gt; fields and immediately think of it as &lt;strong&gt;integers&lt;/strong&gt;. However, the consuming service has no control over the data it receives and the data type is only &lt;em&gt;assumed&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;If you use that distributed ID field as a local foreign key: some external system controls the value and an unforeseen change might break our setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identification
&lt;/h2&gt;

&lt;p&gt;It has proven valuable to us to use the pattern &lt;code&gt;*_identifier&lt;/code&gt; to indicate that…&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it &lt;em&gt;is&lt;/em&gt; some kind of a unique identifier&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;some other&lt;/em&gt; system has control over it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the &lt;code&gt;*_identifier&lt;/code&gt; value is to be stored, it should always be &lt;em&gt;saved as a string type&lt;/em&gt;. Almost anything can be coerced into a string, and that way we guarantee that the origin system can choose whatever they want for their unique identifier.&lt;/p&gt;

&lt;p&gt;This is particularly true if the origin system decided to move to using &lt;strong&gt;UUIDs&lt;/strong&gt;. A final version of the local relational table above could look like this:&lt;/p&gt;
Fig. 4: local relational table, rev. 4


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------+--------------+----
| company_identifier                   | company_name | …
+--------------------------------------+--------------+----
| 328129ae-df4e-4168-94d3-2572b4b343ef | Marley Spoon | …
+--------------------------------------+--------------+----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Payload
&lt;/h2&gt;

&lt;p&gt;If the system &lt;em&gt;exposing&lt;/em&gt; the data is controlled by your organisation, we can support this at the source.&lt;/p&gt;

&lt;p&gt;It is a common pitfall of API designs, especially RESTful APIs, to expose a resource exactly like you represent it in your database. This makes sense to reduce the cognitive load of the team maintaining the API. However, the data layer will inevitably change, rendering this point moot: the DB representation has to be translated to maintain a stable contract. Why not abstract from the data layer to begin with and name the keys in the payload in a system-agnostic way?&lt;/p&gt;
Fig. 5: JSON payload abstracted from persistence


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"company_identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"328129ae-df4e-4168-94d3-2572b4b343ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marley Spoon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"backend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"elixir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;*_identifier&lt;/code&gt; instead of &lt;code&gt;*_id&lt;/code&gt; fields for externally owned data&lt;/li&gt;
&lt;li&gt;Prefer a string type over integer for &lt;code&gt;*_identifier&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;Avoid a 1:1-map of your persistence model to your external API &lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>backend</category>
      <category>api</category>
      <category>shorts</category>
    </item>
    <item>
      <title>Taming Time: how to install &amp; develop with XTDB</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Wed, 05 Apr 2023 14:43:22 +0000</pubDate>
      <link>https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf</link>
      <guid>https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f"&gt;previous article&lt;/a&gt;, we discussed the concept of bitemporality and how it can be used to solve complex architectural problems.&lt;/p&gt;

&lt;p&gt;At MarleySpoon, we’ve used XTDB (or ’XT’ for short) for our new order management system &lt;em&gt;(OMS)&lt;/em&gt;, and discovered a lot of interesting insights about the database itself, the concept of bitemporality, and how developing a project using an immutable, bitemporal database could look like.&lt;/p&gt;

&lt;p&gt;In this article, we will be focusing primarily on our development experience (installing, testing with XTDB) from an Elixir application, and we cover more details about deploying, running XT in cluster, and tuning in production in the upcoming article.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is XTDB
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://xtdb.com/"&gt;XTDB&lt;/a&gt;, or Cross-Time Database, is a distributed and transactional database system designed to handle complex and changing data with ease.&lt;br&gt;
It is based on a bitemporal model, which allows for the tracking of both the valid time and transaction time of data, enabling powerful and flexible querying capabilities.&lt;br&gt;
With XTDB, developers can work with immutable data structures, which simplifies development and improves reliability.&lt;br&gt;
Its graph query language, Datalog, provides a powerful and expressive way to navigate relationships within the data.&lt;/p&gt;

&lt;p&gt;As we’ve illustrated before, XT has a lot of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Bitemporal&lt;/li&gt;
&lt;li&gt;  Supports retroactive corrections&lt;/li&gt;
&lt;li&gt;  Document and graph-based&lt;/li&gt;
&lt;li&gt;  Flexible data schema&lt;/li&gt;
&lt;li&gt;  Unbundled (can be deployed on top of a lot of other DBs and persistence solutions)&lt;/li&gt;
&lt;li&gt;  Can be used within JVM or through REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As one can see, XT is quite different from most of the widely used SQL and NoSQL databases - and while it provides great benefits for dealing with immutable data and retroactive corrections, it also requires an understanding of some of its implementation principles.&lt;/p&gt;
&lt;h1&gt;
  
  
  How we planned to use XTDB
&lt;/h1&gt;

&lt;p&gt;At &lt;a href="//marleyspoon.com"&gt;MarleySpoon&lt;/a&gt;, we ship boxes with recipes and ingredients to our customers. The &lt;strong&gt;orders&lt;/strong&gt; and our subscription model are the backbone of the whole bussiness logic and that is also reflected in how we build our software.&lt;/p&gt;

&lt;p&gt;The core of the orders system is the orders state machine, and although the states are essentially simple, there might still be cases where we could have discrepancies - and in such cases, we would like to have more options to debug or restore orders to a previous state, as well as &lt;em&gt;retroactively&lt;/em&gt; correct their data and push the change to dependent subsystems.&lt;/p&gt;

&lt;p&gt;The shift from the legacy monolith architecture to service-oriented architecture coupled with the introduction of new OMS also required us to be more careful when it comes to eventual consistency - the &lt;strong&gt;transaction&lt;/strong&gt; and &lt;strong&gt;valid&lt;/strong&gt; times can be different in the resulting systems so we can’t just work around it by using the persistence stack we’ve used to (e.g. relational DBs with updates in place).&lt;/p&gt;

&lt;p&gt;Initially, we considered adding transaction and valid time columns to PostgreSQL to implement bitemporality, as this seemed like a straightforward solution. However, upon further analysis, we realized that this approach would introduce significant complexity to the system design. In particular, any foreign keys would need to take the bitemporal columns into account, meaning that queries would need to consider both the entity relationship and its temporal context. This would require significant changes to the database schema, query design, and application code, and would likely lead to a higher risk of errors and data inconsistencies.&lt;/p&gt;

&lt;p&gt;We also considered event sourcing for our needs, but it would add significant incidental complexity, requiring changes to other services’ architectures and a significant amount of application-level code changes to ensure that all events were captured and persisted correctly.&lt;/p&gt;

&lt;p&gt;After considering various approaches to implementing bitemporality in our system, we decided to give XTDB a try due to its native support for bitemporality and graph database capabilities. We designed our data model around XTDB’s capabilities and incorporated the database into our Elixir application.&lt;/p&gt;
&lt;h1&gt;
  
  
  Installing XTDB
&lt;/h1&gt;

&lt;p&gt;There are multiple ways to install and use XTDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Use it as a JVM dependency by simply adding it to your JVM project:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;  Using a pre-built XTDB JAR on a local machine&lt;/li&gt;
&lt;li&gt;  Through a &lt;a href="https://hub.docker.com/r/juxt/xtdb-in-memory"&gt;Docker image&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;XTDB is developed in the Clojure programming language and it is very convenient to run it from any Clojure program - so we’ve decided to write a simple app in Clojure that would run XTDB for us and provide all required setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing dependencies
&lt;/h2&gt;

&lt;p&gt;We’ve installed Clojure using &lt;span&gt;asdf&lt;/span&gt; version manager as it’s very convenient to pin JVM and Clojure versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;# .tool-versions&lt;/span&gt;
    openjdk-18
    clojure 1.11.0.1100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step was creating a new Clojure app using &lt;a href="https://clojure.org/guides/deps_and_cli"&gt;deps CLI&lt;/a&gt; - all of the necessary dependencies were provided in a single file &lt;em&gt;(deps.edn)&lt;/em&gt;.&lt;br&gt;
XTDB is very modular so we have to install PostgreSQL support, HTTP client and server, metrics, and other tools as separate packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org.clojure/clojure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.11.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; Persistence&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;org.postgresql/postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"42.2.18"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-jdbc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-rocksdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; HTTP Client&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-http-client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; HTTP Server&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-http-server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Application entry point &amp;amp; configuration
&lt;/h2&gt;

&lt;p&gt;One of the primary reasons we developed a wrapper for XTDB was to enable us to run an XTDB cluster in a Kubernetes environment. We wanted to simplify the setup process by allowing configuration through environment variables, rather than relying on external configuration files. This allowed us to easily manage XTDB’s configuration within Kubernetes and provided us with greater flexibility in managing our XTDB cluster.&lt;/p&gt;

&lt;p&gt;We’ve created a &lt;span&gt;xtdb.clj&lt;/span&gt; file that is the entry point to the database wrapper and which also has all the required configuration there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:dbname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_DB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_USER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.http-server/server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:jwks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"XTDB_JWKS"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; auth&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb.rocksdb/block-cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb/-&amp;gt;lru-block-cache&lt;/span&gt;&lt;span class="w"&gt;
                                  &lt;/span&gt;&lt;span class="no"&gt;:cache-size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; RocksDB cache size&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/index-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:kv-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb/-&amp;gt;kv-store&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:db-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/tmp/xtdb/indexes"&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:checkpointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-config&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:block-cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.rocksdb/block-cache&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:metrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb.metrics/-&amp;gt;metrics&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:dialect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc.psql/-&amp;gt;dialect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:pool-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:maximumPoolSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:db-spec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/tx-log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;tx-log&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/document-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;document-store&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the initial configuration is done we can provide a simple entry point function that will start an XT node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;;; XT node - hydrated on start&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xt-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;-main&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s"&gt;"Starts a new XTDB node"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xtdb/start-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Started a new XT node ..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seed/seed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; seed data that we need on start&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xtdb/sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Loaded data into a new XT node"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;xt-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; set xt-node with the started node&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c1"&gt;;; Can be used to run REPL or local XT instance&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;; As running it from CLI can assume passing some command line arguments&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;; we should accept a list of optional arguments as a function param&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;;; Runs -main function to start a new XT node&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-main&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Running XT on a local machine
&lt;/h1&gt;

&lt;p&gt;Once our XT app is installed and configured, we can run &lt;code&gt;clj -X xtdb.core/start&lt;/code&gt; in order to start it on a local machine.&lt;br&gt;
This will enable web UI and REST API on &lt;span&gt;&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting from REPL
&lt;/h2&gt;

&lt;p&gt;To run XT in REPL we can instead just execute &lt;span&gt;clj&lt;/span&gt; in shell, given that we are in the root directory of the Clojure project.&lt;/p&gt;

&lt;p&gt;That will start a new Clojure REPL, and if we want to start XT from there, it’s sufficient to use functions we’ve implemented beforehand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; switches current namespace to XT wrapper's core namespace&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting to a remote node
&lt;/h2&gt;

&lt;p&gt;Another important benefit of XT that we unfortunately didn’t explore enough is that by using the &lt;span&gt;http-client&lt;/span&gt; dependency, we’re able to connect to any remote node that is accessible to us by HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xt/new-api-client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xt/submit-tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="no"&gt;::xt/put&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xt/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;}]])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; submits a transaction on the remote node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, we can connect from REPL to any production XT instance and run queries / submit transactions there.&lt;/p&gt;

&lt;h1&gt;
  
  
  REST API
&lt;/h1&gt;

&lt;p&gt;XT is very convenient to use from any Clojure or JVM-based application, however, for clients implemented in other programming languages or different virtual machines, we should be using&lt;a href="https://xtdb.com/docs/"&gt; XT’s REST API&lt;/a&gt;.&lt;br&gt;
XT has a very rich HTTP API that covers most of its functionality &lt;em&gt;(although some of it is only available by Clojure/Java client)&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  API formats
&lt;/h2&gt;

&lt;p&gt;XT supports multiple formats: &lt;em&gt;application/edn, application/json&lt;/em&gt; and &lt;em&gt;application/transit+json&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As XT is written in Clojure and it natively supports Clojure’s data types, we were not satisfied with available JSON types and decided to give &lt;a href="https://github.com/edn-format/edn"&gt;EDN&lt;/a&gt; a try - that way we would have way more supported types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  symbols, e.g. Elixir atoms&lt;/li&gt;
&lt;li&gt;  decimals&lt;/li&gt;
&lt;li&gt;  dates and timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, we had some issues with encoding Elixir/BEAM VM terms to EDN and, in general, the performance of the format - so the &lt;a href="https://github.com/cognitect/transit-format"&gt;Transit/JSON&lt;/a&gt; would be an improvement as apart from being compatible with regular JSON and essentially more performant it also has a more precise types conversion.&lt;/p&gt;
&lt;h2&gt;
  
  
  Main endpoints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/status&lt;/span&gt; - can be used as a health check for an XT node&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/entity&lt;/span&gt; - gets a single entity from the database&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/query&lt;/span&gt; - performs a single Datalog query&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;POST /_xtdb/submit-tx&lt;/span&gt; - submits database transactions&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/await-tx&lt;/span&gt; - waits until the transaction was indexed on the node&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/tx-committed&lt;/span&gt; - checks if the transaction was successfully committed&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Elixir HTTP client
&lt;/h1&gt;

&lt;p&gt;As at that point in time when we started applying XT at MarleySpoon there were no Elixir libraries fully supporting XT’s REST API and covering our needs so we’ve started writing our own HTTP adapter.&lt;/p&gt;
&lt;h3&gt;
  
  
  Umbrella application
&lt;/h3&gt;

&lt;p&gt;As OMS was the only project where we were trying XTDB, we’ve decided to move all the Clojure code, as well as the new Elixir HTTP into the same umbrella app:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
  ...
  xtdb/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That way we could also easily start an XTDB node right in our project and use it from the Elixir application, e.g. by starting through docker-compose.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTTP2
&lt;/h3&gt;

&lt;p&gt;XT’s REST API supports the second revision of HTTP format out of the box - which means that we can have a stable and more performant connection between XT clients (application servers) and the database instances.&lt;/p&gt;

&lt;p&gt;When running an XTDB cluster in production, HTTP2 can be particularly useful. By using HTTP2, direct connections can be established between the client application and an XTDB node, rather than relying on load balancing across multiple instances. Since XTDB doesn’t enforce an equal state between nodes, the same request could yield different results on different nodes - but using HTTP2 eliminates the issue and ensures consistent results for each request.&lt;/p&gt;

&lt;p&gt;However, not every Elixir’s HTTP client supports sending requests using HTTP2 - so we have to search for another option rather than using HTTPoison that we widely use in other projects.&lt;br&gt;
We’ve decided to go with &lt;a href="https://github.com/sneako/finch"&gt;Finch&lt;/a&gt;, as apart from supporting HTTP2 it also focuses on performance and provides telemetry support out of the box - which we’ve found very useful for tracing and debugging purposes.&lt;/p&gt;

&lt;p&gt;Using HTTP2 with Finch requires some initial configuration as we have to have just one connection in a connection pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# apps/xtdb/lib/application.ex&lt;/span&gt;
    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&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;Finch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;pools:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
              &lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;count:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;protocol:&lt;/span&gt; &lt;span class="ss"&gt;:http2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;1&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="p"&gt;]&lt;/span&gt;

    &lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Telemetry &amp;amp; OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;As was mentioned in a previous section, using Finch as an HTTP Client library is a great step to have a seamless telemetry integration.&lt;/p&gt;

&lt;p&gt;Finch provides the next Telemetry events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  request start/stop&lt;/li&gt;
&lt;li&gt;  request exception&lt;/li&gt;
&lt;li&gt;  queue start/stop/exception&lt;/li&gt;
&lt;li&gt;  connection start/stop/exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and others, but for our purpose, we will be more interested only in the first two events.&lt;/p&gt;

&lt;p&gt;In order to integrate the XTDB client with OpenTelemetry we wrote a simple module that watches Finch’s telemetry events and pushes them to the OpenTelemetry collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Checking for optional opentelemetry dependency&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ensure_compiled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="nv"&gt;@tracer_id&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;

          &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
          &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;attach_http_request_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;attach_http_request_stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="ss"&gt;:ok&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;attach_http_request_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="ss"&gt;:telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:http_request_start&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;@http_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:start&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http_request_start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&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="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;attach_http_request_stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="ss"&gt;:telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:http_request_stop&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;@http_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http_request_stop&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&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="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_http_request_start&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;_measurements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;request:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;_config&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="no"&gt;Otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"XT &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request_url&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="p"&gt;%{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
              &lt;span class="ss"&gt;kind:&lt;/span&gt; &lt;span class="ss"&gt;:internal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;attributes:&lt;/span&gt; &lt;span class="n"&gt;attributes&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="n"&gt;handle_http_request_stop&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;_measurements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;request:&lt;/span&gt; &lt;span class="n"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;status:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="n"&gt;_config&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_current_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_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;Span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:"http.status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;...&lt;/span&gt;
            &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_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;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encoding EDN
&lt;/h3&gt;

&lt;p&gt;In Elixir are using &lt;a href="https://github.com/jfacorro/Eden"&gt;Eden&lt;/a&gt; library in order to decode and encode data from and to EDN format. In most cases it works without any issues, however, for decimals we had to implement protocol support for Elixir &lt;em&gt;Decimal&lt;/em&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Implements Eden.Encode protocol for Decimal structs&lt;/span&gt;
    &lt;span class="k"&gt;defimpl&lt;/span&gt; &lt;span class="no"&gt;Eden&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;for:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# Adds a decimal digit number so it can be picked by EDN&lt;/span&gt;
      &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Decimal&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="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&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="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"M"&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;That way you can also implement support of any other custom type or struct that you might need to persist in XT.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing XTDB
&lt;/h1&gt;

&lt;p&gt;Because XTDB is an immutable database, it’s not so simple to delete data from it. This can have implications when it comes to testing, as traditional methods of clearing and resetting a database may not be effective. To integrate XT into a test suite, the most straightforward approach is to run an in-memory node alongside the suite. This allows for more granular control over data, as the in-memory node can be reset or recreated as needed.&lt;/p&gt;

&lt;p&gt;We’ve achieved that by using the in-memory XTDB Docker image in our docker-compose setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Wait for Docker container to be ready&lt;/span&gt;
    &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dokku/wait&lt;/span&gt;

    &lt;span class="na"&gt;xtdb_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juxt/xtdb-in-memory:1.21.0&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and also created a simple shell script that we use in order to run the integration test suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
    docker-compose up &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; xtdb_test
    docker-compose run &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; xtdb_test:3000
    docker-compose run mix_test
    docker-compose stop xtdb_test mix_test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the case of unit tests, as any request coming through the XTDB client is basically an HTTP request, we could also use VCR cassettes as mocks in order to avoid sending real requests to the test instances.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next?
&lt;/h1&gt;

&lt;p&gt;In this article, we’ve discussed how XTDB can be used alongside applications written in Elixir and demonstrated how to implement a simple HTTP client for working with the database. We also covered how to develop and test applications using XTDB, and the importance of running an in-memory node for testing purposes.&lt;/p&gt;

&lt;p&gt;In the third and final part of this series, we’ll be sharing how we used Docker and docker-compose to set up a local development environment for XTDB, as well as how we deployed and ran it in production. We’ll also be discussing caveats and issues we encountered, and how we addressed them.&lt;/p&gt;

&lt;p&gt;We can also recommend some more reading on the topic if you’re interested in developing with XTDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/docs/"&gt;https://xtdb.com/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/blog/xtdb-command-line/"&gt;https://xtdb.com/blog/xtdb-command-line/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy Hacking and stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; for the review!&lt;/em&gt;&lt;br&gt;
&lt;small&gt;Cover image credit: DALL·E 2.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>bitemporality</category>
      <category>elixir</category>
      <category>clojure</category>
      <category>database</category>
    </item>
    <item>
      <title>Constant Doubts</title>
      <dc:creator>Carsten Zimmermann</dc:creator>
      <pubDate>Thu, 23 Mar 2023 12:58:47 +0000</pubDate>
      <link>https://dev.to/marleyspoon/constant-doubts-2gbm</link>
      <guid>https://dev.to/marleyspoon/constant-doubts-2gbm</guid>
      <description>&lt;p&gt;I always had mixed feelings towards Ruby constants. First, &lt;strong&gt;they're not that constant&lt;/strong&gt; to begin with. You can reassign a constant at runtime freely and all you get is a warning:&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;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:bar&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:lol&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;warning: &lt;/span&gt;&lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;initialized&lt;/span&gt; &lt;span class="n"&gt;constant&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;warning: &lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contrary to popular belief, a &lt;code&gt;#freeze&lt;/code&gt; won't help, either. Yes, it makes the &lt;em&gt;object&lt;/em&gt; you're assigning immutable, but it doesn't prevent another constant &lt;em&gt;assignment&lt;/em&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="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"baz"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;warning: &lt;/span&gt;&lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;initialized&lt;/span&gt; &lt;span class="n"&gt;constant&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;warning: &lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;003&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# 🤷&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;(It would be nice to be able to change warning into an error, but there's certainly no &lt;code&gt;RUBYOPT&lt;/code&gt; flag that I know of.)&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;But most importantly: unless they're defined in the top level object space, &lt;strong&gt;I consider them implementation details&lt;/strong&gt;. If an instance of &lt;code&gt;A::Nested::Class&lt;/code&gt; accesses  &lt;code&gt;Some::Other::CONST&lt;/code&gt;, it crosses 5-6 boundaries (depending on where you start counting). That's not actually respecting that module's privacy and also a violation of the &lt;a href="https://en.wikipedia.org/wiki/Law_of_Demeter" rel="noopener noreferrer"&gt;Law of Demeter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;private_constant&lt;/code&gt; all the things!
&lt;/h2&gt;

&lt;p&gt;By default, constants are public. Most software engineers are very eager to work with the &lt;code&gt;private&lt;/code&gt; keyword to limit the public API of their instances, but it's rarer to see that same rigor applied to class or module-level constants.&lt;/p&gt;

&lt;p&gt;Ruby has &lt;code&gt;private_constant&lt;/code&gt; since basically forever (&lt;small&gt;MRI v1.9.3 to be precise&lt;/small&gt;). It accepts one or more symbols referring to defined constants in its scope:&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;MyModule&lt;/span&gt;
  &lt;span class="no"&gt;FOO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
  &lt;span class="no"&gt;LOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:wat?&lt;/span&gt;

  &lt;span class="n"&gt;private_constant&lt;/span&gt; &lt;span class="ss"&gt;:FOO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:LOL&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;foo&lt;/span&gt;
    &lt;span class="no"&gt;FOO&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;Any attempt to access &lt;code&gt;MyModule::FOO&lt;/code&gt; from outside &lt;code&gt;MyModule&lt;/code&gt; will raise a &lt;code&gt;NameError&lt;/code&gt; ("private constant MyModule::FOO referenced").&lt;/p&gt;

&lt;p&gt;Please note that &lt;code&gt;MyModule.foo&lt;/code&gt; still works (and returns the frozen string &lt;code&gt;"bar"&lt;/code&gt;) as it only accesses the private constant from within its defining scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  (Singleton) Methods Over Constants
&lt;/h2&gt;

&lt;p&gt;I mentioned I consider constants implementation details. A named identifier for a magic value, maybe something configurable and set at load time. And sometimes, you want to expose that to other components in your applications.&lt;/p&gt;

&lt;p&gt;As shown in the code snippet above, you can always create a singleton method / module function that wraps around a private constant.&lt;/p&gt;

&lt;p&gt;In my opinion, methods are in all cases superior to &lt;code&gt;CONSTANTS&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;you can defer (lazy load) the assignment&lt;/li&gt;
&lt;li&gt;you can memoize (&lt;code&gt;@class_var ||= ...&lt;/code&gt;) what's being assigned&lt;/li&gt;
&lt;li&gt;you can delegate the method call&lt;/li&gt;
&lt;li&gt;it's easier to stub a method than a constant in your tests&lt;/li&gt;
&lt;li&gt;it pairs well with making class/module-level value configurable on the application level, e.g. using a well-known &lt;code&gt;MyModule.configure(&amp;amp;block)&lt;/code&gt; format or by using &lt;a href="https://dry-rb.org/gems/dry-configurable/0.16/" rel="noopener noreferrer"&gt;dry-config&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;it feels much more OOP to send a message to an object than working with its constants (&lt;small&gt;also, I always feel that CONSTANTS YELL AT ME in the source code&lt;/small&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Points 1..5 all give you a great forward compatible way to refactor how your magic value is used, all for the small price of making your constants private and adding a getter singleton method around it if you really need to expose it to the outside world right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforce Explicit Constant Visibility
&lt;/h2&gt;

&lt;p&gt;Unfortunately, there is no way to make all constant private by default, but RuboCop includes a &lt;a href="https://docs.rubocop.org/rubocop/1.46/cops_style.html#styleconstantvisibility" rel="noopener noreferrer"&gt;&lt;code&gt;RuboCop::Cop::Style::ConstantVisibility&lt;/code&gt;&lt;/a&gt; cop to at least make the constant scope explicit.&lt;/p&gt;

&lt;p&gt;I like that it makes you stop and think about what you're doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Named Classes Are Constants
&lt;/h2&gt;

&lt;p&gt;Now, constants aren't only referred to by &lt;code&gt;UPPERCASE&lt;/code&gt; identifiers: all class and module names are in fact constants, so you could argue that my criticism about accessing &lt;code&gt;Some::CONSTANT&lt;/code&gt; directly must also extend to a form like &lt;code&gt;Nested::Class.new&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And indeed it does, but that will be the topic of the next article. 😀&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Cover image credit: DALL·E 2.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>opinion</category>
    </item>
    <item>
      <title>Bitemporality, or how to change the past</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Fri, 06 Jan 2023 08:34:00 +0000</pubDate>
      <link>https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f</link>
      <guid>https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f</guid>
      <description>&lt;p&gt;We can definitely see the whole history of humanity as a chain of &lt;em&gt;events&lt;/em&gt;:&lt;br&gt;
tiny events, big events, huge events, crucial events – some of them were negligible and some, on the contrary, predefined the next link in the chain and changed the way our world looks now.&lt;/p&gt;

&lt;p&gt;However, the more details we dig out about our history, the more white spots we erase, the more surprising and even contradictious things we see – things that sometimes can completely refute our views and assumptions. Things that we thought and believed to be &lt;strong&gt;facts&lt;/strong&gt; can be invalidated – and we will need to &lt;strong&gt;alter the past&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every kind of record system is not perfect and can contain errors. But what if we wanted to build a system that is resilient to such errors? What if we wanted to change the past but keep the history as a track of facts?&lt;/p&gt;
&lt;h1&gt;
  
  
  Updates in place
&lt;/h1&gt;

&lt;p&gt;Throughout almost all the existence of computers and computer science, resources were the biggest limitation and one of the most complex issues.&lt;/p&gt;

&lt;p&gt;Our progress allowed us to switch from bytes to kilobytes, from kilobytes to mega and gigabytes. Modern PCs, smartphones, and cloud systems can easily handle virtually unlimited amounts of data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The storage&lt;/strong&gt;, which was physically enormous at the beginning of the computer era and started with devices taking over the halls and rooms, moved to CDs, floppy disks and hard drives – so we could even carry movies, tons of photos and music in our pockets. The storage, which always seemed to be the problem, became the smallest of our headaches when it came to building software.&lt;/p&gt;

&lt;p&gt;Nonetheless, the habit &lt;em&gt;(the bad one)&lt;/em&gt; we inherited from the dawn of the computer era is still there and arguably is one of the biggest developer’s headaches – &lt;strong&gt;mutability&lt;/strong&gt;. Before the progress in the storage capacity, we were very careful with how we consumed the RAM and disk space, and that forced us to mutate variables and data instead of persisting them in the immutable way, i.e. saving a new record instead of updating an existing one.&lt;/p&gt;

&lt;p&gt;That habit is still haunting us, even though functional programming languages and &lt;a href="https://en.wikipedia.org/wiki/Immutable_object"&gt;the immutability approach&lt;/a&gt; are on the rise, we still &lt;em&gt;(mostly)&lt;/em&gt; update our records in place – as that is the way most popular databases are built.&lt;/p&gt;
&lt;h2&gt;
  
  
  An audit system
&lt;/h2&gt;

&lt;p&gt;How can we design a record tracking system using a common mutable DB? Let’s say we use PostgreSQL and define a simple &lt;code&gt;audits&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;However, one and a half months ago we’ve discovered that Max forgot to write down the records for BullSheepInc — and as the column has default of &lt;code&gt;0&lt;/code&gt;, we were unaware of the change for quite some time. Now, if we want to fix it, we have to &lt;strong&gt;overwrite&lt;/strong&gt; the existing record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The drawbacks of such an approach are clearly visible: we’ve lost track of the changes and forgot about the mistake. As our reports were already sent, there won’t be any punishment for BullSheepInc.&lt;/p&gt;
&lt;h1&gt;
  
  
  Temporality
&lt;/h1&gt;

&lt;p&gt;The next step from mutability to our goal of designing a perfect track record system will be to &lt;strong&gt;persist changes as facts&lt;/strong&gt;, instead of updating data in place.&lt;br&gt;
  In order to build a truly immutable system we want to disallow overriding records in our audit system. Instead, we will be storing any kind of data change as a&lt;br&gt;
separate row in the database:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It looks like this option worked out better for us; now we see that the inventory record change was tracked and we’ve actually discovered some issues.&lt;br&gt;
We could stop there and pretend we’ve built the most advanced audit system, but that would be too far away from being the truth, as soon we’ve got yet another request …&lt;/p&gt;
&lt;h1&gt;
  
  
  Retroactive changes
&lt;/h1&gt;

&lt;p&gt;Once our employees started seeing not just audit records but also data fixes, John has recalled that it was actually him who did the counting, and the amount of found issues was actually &lt;em&gt;99&lt;/em&gt; instead of &lt;em&gt;101&lt;/em&gt;.&lt;br&gt;
We’ve got a serious problem now as the new record doesn’t fit into our data model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Max&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;99&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;John&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;01.05.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which record is really &lt;em&gt;valid&lt;/em&gt; now? Should we trust Max or John? How should we define what was the error and &lt;strong&gt;how it was corrected&lt;/strong&gt; ?&lt;br&gt;
That’s where the concept of &lt;strong&gt;bitemporality&lt;/strong&gt; comes to the rescue.&lt;/p&gt;
&lt;h1&gt;
  
  
  Bitemporality
&lt;/h1&gt;

&lt;p&gt;In the example above, we have only one time column: the record, or &lt;em&gt;transaction date&lt;/em&gt;.&lt;br&gt;
Bitemporality assumes adding another time dimension — the so-called &lt;strong&gt;valid time&lt;/strong&gt; or effective time — along the &lt;strong&gt;transaction time&lt;/strong&gt; for tracking &lt;strong&gt;when the change really happened&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction time&lt;/strong&gt; represents the time when the record was inserted into the data storage. This can be quite useful for audit purposes, tracking changes and event sourcing.&lt;br&gt;
&lt;strong&gt;Valid time&lt;/strong&gt; represents when the change became &lt;em&gt;valid&lt;/em&gt; and happened in the real world.&lt;/p&gt;

&lt;p&gt;If we follow these definitions we can say that &lt;em&gt;transaction&lt;/em&gt; time is the time we &lt;em&gt;thought&lt;/em&gt; the data was correct at that point in time — and it was &lt;em&gt;actually&lt;/em&gt; correct on the &lt;strong&gt;valid&lt;/strong&gt; time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On the 15th of February, we’ve thought Max has not found any issues.&lt;br&gt;
On the 1st of April, Max corrected the number of issues to be 101.&lt;br&gt;
On May 1st, we’ve discovered that John actually found 99 issues.&lt;br&gt;
In reality, we want the actual amount of issues recorded to be 99 as of 15th of February.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a bitemporal system transaction time is immutable and can be only increased while valid time can be any past or future timestamp.&lt;br&gt;
Let’s see how we can redesign the audit system using these two time dimensions:&lt;/p&gt;
&lt;h2&gt;
  
  
  The perfect audit system™
&lt;/h2&gt;

&lt;p&gt;Now that we know how to utilise transaction and valid dates, we can change our records by writing the record time as &lt;em&gt;transaction date&lt;/em&gt; and&lt;br&gt;
time when it became valid as &lt;em&gt;valid time&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Transaction date&lt;/th&gt;
&lt;th&gt;Valid date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;John&lt;/td&gt;
&lt;td&gt;01.05.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let’s execute some queries to our database:&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;Audit&lt;/span&gt;
      &lt;span class="c1"&gt;# @return [Hash] a hash with auditor, issues found and transaction date fields&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_date&lt;/span&gt; &lt;span class="o"&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;transaction_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# calling the DB ...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "John", issues_found: 99, transaction_date: "01.05.2022"}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"15.09.2021"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "Joe", issues_found: 180, transaction_date: "15.09.2021"}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"01.01.2021"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# nil - we didn't inspect the company as of 01.01.2021&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"15.02.2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"01.04.2022"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "Max", issues_found: 101, transaction_date: "01.04.2022"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, now we have the latest correct value returned by default,&lt;br&gt;
but we can also fetch the record the record as it was on a given valid date in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use-cases
&lt;/h2&gt;

&lt;p&gt;Bitemporality can be proven useful for any system where you have a track of the data and where it’s &lt;em&gt;possible&lt;/em&gt; to have errors and recover them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  payrolls, payment systems&lt;/li&gt;
&lt;li&gt;  auditing&lt;/li&gt;
&lt;li&gt;  risk systems&lt;/li&gt;
&lt;li&gt;  blockchains&lt;/li&gt;
&lt;li&gt;  insurance&lt;/li&gt;
&lt;li&gt;  compliance &amp;amp; privacy&lt;/li&gt;
&lt;li&gt;  temporal data management&lt;/li&gt;
&lt;li&gt;  event-based systems&lt;/li&gt;
&lt;li&gt;  distributed transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Cross-time Database
&lt;/h1&gt;

&lt;p&gt;Supporting bitemporality in an existing database might be not a trivial task, especially when it comes to the traditional relational database where we all relations between tables should also take into account bitemporal columns.&lt;/p&gt;

&lt;p&gt;At the moment, the most prominent open-source solution is &lt;a href="https://xtdb.com"&gt;XTDB (or cross-time) database&lt;/a&gt; developed by &lt;a href="https://juxt.pro"&gt;JUXT&lt;/a&gt; which has a lot of benefits compared to its competitors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Bitemporal at its core&lt;/li&gt;
&lt;li&gt;  Supports retroactive corrections&lt;/li&gt;
&lt;li&gt;  Document&amp;amp;graph based (ultimately a store of versioned documents)&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://en.wikipedia.org/wiki/Datalog"&gt;Datalog queries&lt;/a&gt; and SQL support&lt;/li&gt;
&lt;li&gt;  Data eviction (supports eviction of active and historical data to assist with technical compliance for information privacy regulations)&lt;/li&gt;
&lt;li&gt;  Distributed and scalable&lt;/li&gt;
&lt;li&gt;  Unbundled database (can be deployed on top of many existing technologies and databases like Kafka, JDBC, AWS S3)&lt;/li&gt;
&lt;li&gt;  Can be easily integrated into any existing JVM application or connected using its REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we will explore more in the next articles, XTDB can be used as a ready solution for building immutable and bitemporal software.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next?
&lt;/h1&gt;

&lt;p&gt;As one can see, bitemporality can be a perfect match for cases where we build systems that are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Tracking the history of changes or how data was changing over time&lt;/li&gt;
&lt;li&gt; Can potentially have errors, corrections or data adjustments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we take that concept as the cornerstone of such systems there will be way more chances they will be successful and&lt;br&gt;
we will escape from mutability issues.&lt;/p&gt;

&lt;p&gt;We can also recommend some more reading on the topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://martinfowler.com/articles/bitemporal-history.html"&gt;Martin Fowler on Bitemporality&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/"&gt;XTDB — the open database with temporal graph query&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.xtdb.com/concepts/bitemporality/"&gt;Bitemporality concept in XTDB docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the upcoming article we will share our experience working with XTDB, connecting to it from an Elixir application and what we learned from it.&lt;/p&gt;

&lt;p&gt;Happy Hacking and stay tuned!&lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; for the review! &lt;/p&gt;

</description>
      <category>architecture</category>
      <category>bitemporality</category>
      <category>database</category>
    </item>
    <item>
      <title>What's the point? The 3 Es.</title>
      <dc:creator>Carsten Zimmermann</dc:creator>
      <pubDate>Tue, 05 Apr 2022 12:00:54 +0000</pubDate>
      <link>https://dev.to/marleyspoon/whats-the-point-the-3-es-1804</link>
      <guid>https://dev.to/marleyspoon/whats-the-point-the-3-es-1804</guid>
      <description>&lt;p&gt;&lt;strong&gt;Musings on the arbitrary number we attach to tasks &amp;amp; stories&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once per moon, we find ourselves discussing both what dimensions we should consider when we’re doing our Backlog Refinement and what types of work should receive Story Points to begin with. Occasionally, the term →“Business Value” is tossed into the conversation, but for my heavily backend-focussed team, it is very difficult to attach that particular metric to a highly technical task – and we currently have a lot of them.&lt;/p&gt;

&lt;p&gt;This article originally aimed to conclude that conversation, a publicised Working Agreement if you will. It may serve as inspiration to others who wish to look at their pointing strategy through the lens of a Backend team. But as with most things on the internet, YMMV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue Types
&lt;/h2&gt;

&lt;p&gt;We work with Product Backlog Items (PBI) that can be categorised as one of the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  Story
&lt;/h3&gt;

&lt;p&gt;Captures &lt;strong&gt;work from an end user’s perspective&lt;/strong&gt;, with varying definitions of what an end user is (for us, it can be another system consuming our public API). They typically follow the format of “&lt;em&gt;As &amp;lt;who&amp;gt; &amp;lt;when&amp;gt; &amp;lt;where&amp;gt;, I want &amp;lt;what&amp;gt; because &amp;lt;why&amp;gt;&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;Stories are pointed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task
&lt;/h3&gt;

&lt;p&gt;Describes an often &lt;strong&gt;technical aspect of an overarching goal&lt;/strong&gt;. This could be a necessary refactoring, foundational work, paying back technical debt, or general housekeeping chores.&lt;/p&gt;

&lt;p&gt;We point Tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug
&lt;/h3&gt;

&lt;p&gt;Represents a defect, something that isn’t working as intended. Bugs have varying priorities: some have a very low impact with known workarounds, the more urgent ones affect production and need to be addressed right away.&lt;/p&gt;

&lt;p&gt;Some Bugs we point, some Bugs we don’t:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-priority bugs that need to be added to current Sprint probably represent rework – a missed edge-case or something that slipped through QA. As such, it should not count towards the team’s →Velocity as that work should have gone into previous Sprints… so to speak&lt;/li&gt;
&lt;li&gt;Bugs for which we have workarounds or whose impact is considered low should be part of the normal prioritisation process. Attaching a number to it will help weighing against other items in the Backlog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Spike
&lt;/h3&gt;

&lt;p&gt;Work to explore a new technology, test new processes, eliminate unknowns (see below, →Entropy). The goal is not to ship production-ready code, but rather to provide a proof of concept. Code produced while working on a Spike might deliberately forgo tests or sad path handling.&lt;/p&gt;

&lt;p&gt;The main &lt;strong&gt;output of a Spike is information&lt;/strong&gt;. The information is shared with the team. We currently do not point Spikes, but we time-box them (~2 days).&lt;/p&gt;

&lt;h3&gt;
  
  
  Epic
&lt;/h3&gt;

&lt;p&gt;A container for related issues of various types. Ideally, they have a defined end-date and all its member issues (Stories &amp;amp; Tasks, mainly) are defined &amp;amp; scoped together to avoid too much context switching.&lt;/p&gt;

&lt;p&gt;Possible anti-pattern: using an Epic to group related tickets in such a way that the Epic grows and grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpreting Points: The Three Es (3Es)
&lt;/h2&gt;

&lt;p&gt;I understand story points to be one of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Entanglement (as in: how many connections / interactions / dependencies)&lt;/li&gt;
&lt;li&gt;Effort (as in: how time consuming is the actual work)&lt;/li&gt;
&lt;li&gt;Entropy (as in: state of disorder, The Unknown)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Entanglement
&lt;/h3&gt;

&lt;p&gt;Entanglement reflects how many moving parts have to be considered, the literal complexity of the feature. Higher complexity will translate to more diligence considering side-effects or edge-cases, as well as requiring a more thorough QA process. &lt;/p&gt;

&lt;h3&gt;
  
  
  Effort
&lt;/h3&gt;

&lt;p&gt;Effort can be a measurement for how laborious a task is, even if it’s not rocket science. An example would be restructuring a code base where the goal is clear, but it will involve renaming many files and module names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entropy
&lt;/h3&gt;

&lt;p&gt;Entropy expresses the uncertainty around a given task. It can cover blind-spots about the implications of a change, lack of experience with the domain or technology, a placeholder for the unknown Unknowns that might be uncovered while working on the task.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: if there’s too much entropy, creating a →Spike first is advisable.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Value
&lt;/h2&gt;

&lt;p&gt;Now, the Three Es don’t include the hallowed Business Value. After all, everything in agile software development is about continuously delivering value, right?&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no objective scale by which we can measure Business Value. For some cases, “revenue added / cost saved” would be a good unit of measure, but “customer satisfaction” is nigh impossible to translate to money, whereas “better fault tolerance” requires a lot of effort (and guesswork) to put a € sign next to it.&lt;/p&gt;

&lt;p&gt;Business Value is also time-dependent: it exists within the context of the organisation’s current situation and direction. Whatever arbitrary value we assign as Business Value for a story today might receive a completely different rating next month when re-evaluated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business Value is not a metric to feed into a team’s Velocity. It’s a means to prioritise work!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Further reading: &lt;a href="https://medium.com/serious-scrum/business-value-9607a9ec02db"&gt;Business Value&lt;/a&gt; by Erik de Bos.&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Velocity &amp;amp; Capacity
&lt;/h2&gt;

&lt;p&gt;In Agile software development processes, the Sprint Velocity is a metric for the output of a team, measured in Story Points completed. Cynically, one could argue that it’s also a metric on how good the team is at estimating, but let’s assume that outliers in estimation are inevitable and are evened out over time.&lt;/p&gt;

&lt;p&gt;A (somewhat) deterministic pointing system where units of work (represented in PBIs) can be compared is key to assess the output of a team. Hopefully, the 3Es above help with that.&lt;/p&gt;

&lt;p&gt;&lt;span id="velocity"&gt;&lt;/span&gt;&lt;br&gt;
Now, &lt;strong&gt;Velocity&lt;/strong&gt; as a metric for a team’s output (i.e. the sum of completed story points) is meaningless if it’s not correlated to the input of the Sprint: the person-days that were available. It’s the Story Points / Available Capacity that really matters. The rationale being: one Sprint might have lower output because ⅓ of the team was recharging their batteries on well-deserved PTO (reduced input).&lt;/p&gt;

&lt;p&gt;If you want to track the relative efficiency of your Sprint, the relation of completed Story Points to Days Available provides the best insight and it also leads to more precise capacity planning: by calculating, say, a 4-Sprint-average of that ratio, you’ll end up with a multiplier to give you a good idea about how many Story Points you might complete in the next Sprint.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sprint&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A: Points completed&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B: Available Days&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity Modified (A/B)&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.63&lt;/td&gt;
&lt;td&gt;0.67&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 4-Sprint-average is (0.5 + 0.63 + 0.67 + 0.82) / 4 = 0.655. If you have 36 person-days available in your upcoming Sprint, you should aim for ~24 Story Points.&lt;/p&gt;

&lt;p&gt;It’s controversial if you want to count unfinished business towards your delivered output. I get why you wouldn’t want that – a User Story that isn’t fully done doesn’t provide value – but we also established that we won’t look at value.&lt;/p&gt;

&lt;p&gt;Being able to accurately plan what can be delivered in a Sprint is much more tangible than the incorporeal value and to that end, we split issues based on what was completed and what will roll over to the next Sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In general, we already have a good grasp on how how many points to attach to a PBI – being primarily an &lt;a href="https://dev.to/t/elixir"&gt;#Elixir&lt;/a&gt; team, we have mentally assigned  &lt;code&gt;@default_points&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; and just use the &lt;del&gt;module&lt;/del&gt; brain attribute in Scrum Poker. This is a good sign, as we're apparently doing ok at splitting work into bite-sized chunks already. But for more complex issues and when the estimates given differ a lot, it's good to do a sanity check by looking at the three dimensions  Entanglement, Effort, and Entropy separately.&lt;/p&gt;

&lt;p&gt;Additionally, finally having resolved that considering Business Value as something that should go into a PBI's story point estimation was a huge win.&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo credit: &lt;a href="https://pxhere.com/en/photo/1188433"&gt;PxHere&lt;/a&gt;, CC0 Public Domain&lt;/small&gt;&lt;/p&gt;

</description>
      <category>scrum</category>
      <category>backend</category>
    </item>
    <item>
      <title>Using VCR to Mock Your Requests</title>
      <dc:creator>Ana Schwendler</dc:creator>
      <pubDate>Fri, 18 Jun 2021 10:05:31 +0000</pubDate>
      <link>https://dev.to/marleyspoon/using-vcr-to-mock-your-requests-3127</link>
      <guid>https://dev.to/marleyspoon/using-vcr-to-mock-your-requests-3127</guid>
      <description>&lt;p&gt;In the past months, I've been working with a lot of external API requests. Since I'm part of the Marley Spoon’s logistics team, our challenge is to make sure our requests are done in time and everything is delivered in good shape.&lt;/p&gt;

&lt;p&gt;Part of this task, as a software developer, is making sure the code I write is well tested, and does what it is supposed to do. Our software has many integrations with our logistics partners. When integrating with their systems, we don't want to hit external APIs with real requests every time we run our tests. This not only generates unnecessary requests that can cause problems with API rate limits and other unwanted side-effects. It also makes them run slower. To avoid that, we pretend to make real requests and there is a tool that helps "faking" these calls: by recording real data from a real network request, we are able to stub a third party’s web service response. The saved data is called a "cassette tape" and the name of the tool I refer to is &lt;a href="https://github.com/vcr/vcr"&gt;VCR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post I'll explain how I've been learning to use it and why I think it is useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/xT1R9RfuBqWvfo8oDe/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/xT1R9RfuBqWvfo8oDe/giphy.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a mock test?
&lt;/h2&gt;

&lt;p&gt;Since this is not about the mocking technique, I'll just briefly introduce it and link to a useful article on the topic. In &lt;a href="https://circleci.com/blog/how-to-test-software-part-i-mocking-stubbing-and-contract-testing/"&gt;this post&lt;/a&gt; we can see mock testing defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mocking means creating a fake version of an external or internal service that can stand in for the real one, helping your tests run more quickly and more reliably. When your implementation interacts with an object’s properties, rather than its function or behavior, a mock can be used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;… and that is exactly the reason why we use VCR.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is VCR?
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://github.com/vcr/vcr"&gt;their documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which means that when you first run your test with the VCR syntax, it will record what happened and next time you run the test again, you will have the recorded version ready to stand in for the real request.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use VCR?
&lt;/h2&gt;

&lt;p&gt;I have a basic example &lt;a href="https://github.com/anaschwendler/vcr_example"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So basically, whenever you want to test a part of code that requires an external web request, you use the  &lt;code&gt;.use_cassette&lt;/code&gt; method to state that you want VCR to deal with that with a cassette file. If there already is data with a pre-recorded HTTP interaction, VCR will use it. If not, VCR will &lt;strong&gt;automatically create&lt;/strong&gt; a cassette file (this time making a real request) based on the request made in the test.&lt;/p&gt;

&lt;p&gt;But still, if you are looking forward to making real requests, VCR also offers different record modes, which can be checked &lt;a href="https://relishapp.com/vcr/vcr/v/6-0-0/docs/record-modes"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example:&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubygems'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'test/unit'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'vcr'&lt;/span&gt;

&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cassette_library_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fixtures/vcr_cassettes"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook_into&lt;/span&gt; &lt;span class="ss"&gt;:webmock&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;VCRTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_example_dot_com&lt;/span&gt;
    &lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"synopsis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://www.iana.org/domains/reserved'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;assert_match&lt;/span&gt; &lt;span class="sr"&gt;/Example domains/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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 you are requesting to use a cassette called &lt;code&gt;synopsis&lt;/code&gt;, which mocks a request to &lt;a href="http://www.iana.org/domains/reserved"&gt;iana.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If this is running the first time, VCR will generate a "cassette" file, which will be stored at &lt;code&gt;fixtures/vcr_cassettes&lt;/code&gt;, which for the example will be called &lt;code&gt;synopsis.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is what &lt;code&gt;fixtures/vcr_cassettes/synopsis.yml&lt;/code&gt; looks like (I've removed some parts as it was too big):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--------
http_interactions:
- request:
    method: get
    uri: http://www.iana.org/domains/reserved
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
      Accept:
      - "*/*"
      User-Agent:
      - Ruby
  response:
    status:
      code: 200
      message: OK
    headers:
      Date:
      - Fri, 18 Jun 2021 08:06:40 GMT
      Server:
      - Apache
      Vary:
      - Accept-Encoding
      Last-Modified:
      - Thu, 21 May 2020 22:41:39 GMT
      X-Frame-Options:
      - SAMEORIGIN
      Expires:
      - Fri, 18 Jun 2021 09:56:45 GMT
      Referrer-Policy:
      - origin-when-cross-origin
      X-Content-Type-Options:
      - nosniff
      Age:
      - '595'
      Content-Type:
      - text/html; charset=UTF-8
      Cache-Control:
      - public, max-age=21603
      Content-Security-Policy: /*cropped security policy*/
      Transfer-Encoding:
      - chunked
    body:
      encoding: ASCII-8BIT
      string: !binary |-
        /*cropped binary string*/
    http_version:
  recorded_at: Fri, 18 Jun 2021 08:06:40 GMT
recorded_with: VCR 5.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on your test suite will use this cassette file. It will also run faster, as it has the file in place and doesn't need to make the request which can take more time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples in real life
&lt;/h2&gt;

&lt;p&gt;One example of project that uses VCR is this &lt;code&gt;site-search-ruby&lt;/code&gt; client from elastic: &lt;a href="https://github.com/elastic/site-search-ruby"&gt;https://github.com/elastic/site-search-ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In their context of &lt;code&gt;Search&lt;/code&gt;, when searching all DocumentTypes in the engine, they use a cassette called &lt;code&gt;engine_search&lt;/code&gt; to mock a request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/elastic/site-search-ruby/blob/master/spec/client_spec.rb"&gt;https://github.com/elastic/site-search-ruby/blob/master/spec/client_spec.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the cassette file that is used in this call: &lt;a href="https://github.com/elastic/site-search-ruby/blob/master/spec/fixtures/vcr/engine_search.yml"&gt;https://github.com/elastic/site-search-ruby/blob/master/spec/fixtures/vcr/engine_search.yml&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;VCR seems to be a very reliable tool to fake requests to APIs. But we still need to be aware that sometimes APIs can change and with that we need to record our tests again. This process should be simple and easy to reproduce, so for more tips regarding VCR use, I recommend &lt;a href="https://fabioperrella.github.io/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html"&gt;this&lt;/a&gt; blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding confidential credentials in vcr_cassettes
&lt;/h3&gt;

&lt;p&gt;There is a configuration option available to filter sensitive data that can be used to prevent it from being written to the cassette files, the documentation is &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/configuration/filter-sensitive-data"&gt;here&lt;/a&gt;. This is important, as you want to track your cassette YML files in your source control management tool. And secrets don’t belong there.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/vcr/vcr"&gt;https://github.com/vcr/vcr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/elastic/site-search-ruby"&gt;https://github.com/elastic/site-search-ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://circleci.com/blog/how-to-test-software-part-i-mocking-stubbing-and-contract-testing/"&gt;https://circleci.com/blog/how-to-test-software-part-i-mocking-stubbing-and-contract-testing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fabioperrella.github.io/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html"&gt;https://fabioperrella.github.io/10_tips_to_help_using_the_VCR_gem_in_your_ruby_test_suite.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://github.com/memunaharuna"&gt;Memuna&lt;/a&gt; for reviewing it! 🎉&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>vcr</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
