<?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: Yota Hamada</title>
    <description>The latest articles on DEV Community by Yota Hamada (@yohamta).</description>
    <link>https://dev.to/yohamta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F105276%2F56a09afa-7c4d-4f18-a13a-ea1285cecbb1.jpeg</url>
      <title>DEV Community: Yota Hamada</title>
      <link>https://dev.to/yohamta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yohamta"/>
    <language>en</language>
    <item>
      <title>Mastering DDD: Repository Design Patterns in Go</title>
      <dc:creator>Yota Hamada</dc:creator>
      <pubDate>Tue, 02 Jan 2024 05:45:00 +0000</pubDate>
      <link>https://dev.to/yohamta/mastering-ddd-repository-design-patterns-in-go-5him</link>
      <guid>https://dev.to/yohamta/mastering-ddd-repository-design-patterns-in-go-5him</guid>
      <description>&lt;p&gt;This article aims to deepen the understanding of "Repository", a core concept in Domain-Driven Design (DDD). We will explore the fundamental roles and importance of a repository and present an example of its implementation in the Go language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Assumes an application that retrieves (and updates) data from a relational database.&lt;/li&gt;
&lt;li&gt;Sample code is written in Go.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a Repository?
&lt;/h2&gt;

&lt;p&gt;Let's start by looking at the &lt;a href="https://martinfowler.com/eaaCatalog/repository.html" rel="noopener noreferrer"&gt;definition&lt;/a&gt; of a repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository Pattern:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A repository maps the data retrieved from the database into a structure and provides an interface to access domain objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This aligns with the general understanding of a repository. Next, let's look at a more detailed definition in the context of DDD.&lt;/p&gt;

&lt;h3&gt;
  
  
  A More Detailed Definition of Repository
&lt;/h3&gt;

&lt;p&gt;Check out the &lt;a href="https://www.domainlanguage.com/ddd/reference/" rel="noopener noreferrer"&gt;DDD Reference&lt;/a&gt;. This reference is based on Eric Evans's book on DDD, summarizing various DDD terms. On page 17, it states:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Repository Definition (DDD Reference):&lt;/strong&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Query access to aggregates expressed in the ubiquitous language.&lt;/p&gt;

&lt;p&gt;Proliferation of traversable associations used only for finding things muddles the model. In mature models, queries often express domain concepts. Yet queries can cause problems.&lt;br&gt;
The sheer technical complexity of applying most database access infrastructure quickly swamps the client code, which leads developers to dumb-down the domain layer, which makes the model irrelevant.&lt;/p&gt;

&lt;p&gt;A query framework may encapsulate most of that technical complexity, enabling developers to pull the exact data they need from the database in a more automated or declarative way, but that only solves part of the problem.&lt;/p&gt;

&lt;p&gt;Unconstrained queries may pull specific fields from objects, breaching encapsulation, or instantiate a few specific objects from the interior of an aggregate, blindsiding the aggregate root and making it impossible for these objects to enforce the rules of the domain model. Domain logic moves into queries and application layer code, and the entities and value objects become mere data containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Role of Repository
&lt;/h3&gt;

&lt;p&gt;The role of a repository is to provide access to aggregates. The aspect of "executing SQL to fill a structure with data and return it" is just a superficial facet of a repository.&lt;/p&gt;

&lt;p&gt;A repository always exists in tandem with an aggregate. Therefore, a correct understanding of aggregates is essential for practicing DDD.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an Aggregate?
&lt;/h2&gt;

&lt;p&gt;Let's look into the term "aggregate". What is an aggregate? On page 16 of the &lt;a href="https://www.domainlanguage.com/ddd/reference/" rel="noopener noreferrer"&gt;DDD Reference&lt;/a&gt;, just above the repository section, there's an explanation of aggregates.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Definition of Aggregate (DDD Reference):&lt;/strong&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Cluster the entities and value objects into aggregates and define boundaries around each. Choose one entity to be the root of each aggregate, and allow external objects to hold references to the root only (references to internal members passed out for use within a single operation only). Define properties and invariants for the aggregate as a whole and give enforcement responsibility to the root or some designated framework mechanism.&lt;/p&gt;

&lt;p&gt;Use the same aggregate boundaries to govern transactions and distribution.&lt;/p&gt;

&lt;p&gt;Within an aggregate boundary, apply consistency rules synchronously. Across boundaries, handle updates asynchronously.&lt;/p&gt;

&lt;p&gt;Keep an aggregate together on one server. Allow different aggregates to be distributed among nodes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What is an Entity?
&lt;/h3&gt;

&lt;p&gt;An entity is also defined in the &lt;a href="https://www.domainlanguage.com/ddd/reference/" rel="noopener noreferrer"&gt;DDD Reference&lt;/a&gt;. An entity is a domain object uniquely identifiable by an ID. In a domain model, there are entities and value objects (Value Object), the latter not having an ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aggregate ≠ "Model"
&lt;/h3&gt;

&lt;p&gt;The term "model" has been used in application development for a long time, but its meaning varies depending on who uses it and the context. In the past, in web application development, "model" meant the model in the MVC (Model-View-Controller) pattern.&lt;/p&gt;

&lt;p&gt;Have you heard of Active Record? It's a design pattern adopted by Ruby on Rails and ORM libraries. In Active Record, a "model" holds data for one record of a table.&lt;/p&gt;

&lt;p&gt;In Ruby on Rails, classes implementing Active Record are called "models" and are placed in the &lt;code&gt;models/&lt;/code&gt; path. Influenced by Rails, in application development, "model" usually refers to an object holding data for one row of a table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggregate in DDD is different from Ruby on Rails's "model"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As mentioned, an aggregate in DDD can hold data not only for the root entity but also for other entities. Therefore, an aggregate is not a 1:1 relationship with a table but a 1:n relationship (n ≥ 1). An aggregate in DDD is not just for manipulating one record of a table.&lt;/p&gt;

&lt;p&gt;An aggregate is a domain model. Even if composed of a single entity, please consider it a type of aggregate. A repository provides an interface to access aggregates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entities Composing an Aggregate
&lt;/h3&gt;

&lt;p&gt;We know an aggregate comprises one or more entities. So, how should we decide which entities compose an aggregate?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.domainlanguage.com/ddd/reference/" rel="noopener noreferrer"&gt;DDD Reference&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An aggregate defines a unit that sets the attributes (properties) and invariants, responsible for maintaining these.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, an aggregate is a unit that defines the attributes of a domain model and is responsible for maintaining the invariants demanded by that domain model.&lt;/p&gt;

&lt;p&gt;Invariants are essentially relationships between data that must always be maintained. That's data consistency. To maintain data consistency (invariants), updates must occur within the same transaction. This forms the basis of an aggregate. In summary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entities Composing an Aggregate:&lt;/strong&gt;&lt;br&gt;
These are clusters of entities and value objects that should be treated as a unit in maintaining database data consistency (invariants). One of these entities becomes the root of the aggregate. Other aggregates can only hold references to the root entity, not the encapsulated entities within an aggregate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Boundaries of an Aggregate
&lt;/h3&gt;

&lt;p&gt;There's no silver bullet for correctly determining the boundaries of an aggregate. Properly setting aggregate boundaries requires consideration of database design, scalability, and domain knowledge. It's more challenging in complex domains, like inventory management systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of Thumb:&lt;/strong&gt;&lt;br&gt;
Keep the boundaries of an aggregate as small as possible. This is to reduce the size of database transactions and, in turn, reduce the technical debt of the domain model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Minimizing Aggregate Boundaries
&lt;/h3&gt;

&lt;p&gt;How can we keep aggregate boundaries small? To do this, we need to consider it from the stage of table design.&lt;/p&gt;

&lt;p&gt;The boundaries of an aggregate will become the boundaries for asynchronous processing when the service or product scales in the future. Therefore, you must decide boundaries such that data inconsistency doesn't occur even when transactions are split. Sometimes, tables need to be separated in unexpected ways.&lt;/p&gt;

&lt;p&gt;As aggregates grow larger, the number of tables updated in synchronous transactions increases, leading to performance degradation and maintenance issues. Even if it seems that entities should be updated in one transaction, they might be separable upon closer examination. It's crucial not to be constrained by preconceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
When splitting transactions at aggregate boundaries, consider the implications of partial transaction failures. Ideally, a single operation in the user interface should not span multiple aggregate boundaries. Since aggregate boundaries are deeply intertwined with the user interface, it's important to align understanding with product managers and designers early in the process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example of a Shopping Cart
&lt;/h2&gt;

&lt;p&gt;Let's consider the interface of a shopping cart for an e-commerce site. Assume there are tables for the shopping cart and cart items. Application layer use case X utilizes the data of the shopping cart and cart items (e.g., add a cart item into a shopping cart).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ER Diagram&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;The interface for a non-DDD approach would look like this, with a one-to-one correspondence between tables and repositories.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repository Interface:&lt;/strong&gt;&lt;br&gt;
Repositories are defined for the ShoppingCart and CartItem tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ShoppingCart&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;GetByUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
  &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CartItem&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;GetByShoppingCartID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CartItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CartItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
  &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CartItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Model Definition:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Structures representing one record of each table are defined as models. Without interfaces, domain logic cannot be encapsulated&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ShoppingCart&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
  &lt;span class="n"&gt;UserID&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCartStatus&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CartItem&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
  &lt;span class="n"&gt;ProductID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Potential Issues:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This approach has the following potential issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Models become mere containers for table records.&lt;/li&gt;
&lt;li&gt;Domain logic is implemented in use case X.&lt;/li&gt;
&lt;li&gt;Aggregate invariants are ensured at the application layer.&lt;/li&gt;
&lt;li&gt;Use case X needs to consider new and updated database operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DDD-Compliant Repository Example
&lt;/h3&gt;

&lt;p&gt;Now, let's design a shopping cart repository following DDD principles.&lt;/p&gt;

&lt;p&gt;The root of the aggregate is the ShoppingCart entity. CartItem entity is encapsulated within the ShoppingCart aggregate.&lt;/p&gt;

&lt;p&gt;First, define a repository corresponding to the ShoppingCart aggregate. Unlike the previous example, there is no CartItem repository. This is because repositories are defined per aggregate, not per table. The ShoppingCart's &lt;code&gt;AddItem&lt;/code&gt; method is an example of domain logic that is used by use case X.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Class Diagram:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repository Interface:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The repository encapsulates the process of reading and updating aggregates, so there's usually no need to separate &lt;code&gt;Insert&lt;/code&gt; and &lt;code&gt;Update&lt;/code&gt;. The repository takes responsibility for this in the &lt;code&gt;Save&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ShoppingCartRepository&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;GetByUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aggregate Model Definition:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The root of the aggregate is the ShoppingCart entity. Aggregates encapsulate domain logic, so they are defined as interfaces, not structures, and are defined in the &lt;code&gt;domain&lt;/code&gt; package. Here's the aggregate interface definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ShoppingCart&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
  &lt;span class="n"&gt;AddItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Improved Points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repository can fully encapsulate database complexity (insertion/update decisions).&lt;/li&gt;
&lt;li&gt;No need to create a repository for each table, reducing redundant SQL coding.&lt;/li&gt;
&lt;li&gt;By interfacing models, domain logic can be encapsulated.&lt;/li&gt;
&lt;li&gt;Domain logic and database complexity are eliminated from the application layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adopting DDD clarifies the responsibilities of domain logic, database access, and the application layer. This improvement reduces application complexity and cognitive load, enhancing development speed over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository and Aggregate Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should Aggregate and Repository Implementations be Separated?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Where should the implementations of the model and repository interfaces be placed? There's no definitive answer, but I personally believe it's preferable to place the implementations of repositories and models in the same package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reasons for Defining Repository and Aggregate Model Implementations in the Same Package:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The role of the repository was to encapsulate the complexity of database access, creation, and saving of aggregates. The repository needs to initialize attributes for instantiating the aggregate. Since it references and updates attributes not exposed in the interface, it needs to be defined in the same package.&lt;/p&gt;

&lt;p&gt;For example, consider the &lt;code&gt;Save&lt;/code&gt; method implementation of the repository. In the following implementation, the decision to register new or update in the database is encapsulated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;shoppingcart&lt;/span&gt;

&lt;span class="c"&gt;// Struct implementing domain.ShoppingCartRepository&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;repositoryImpl&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c"&gt;// Struct implementing domain.ShoppingCart&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;shoppingCartImpl&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
  &lt;span class="c"&gt;// other properties...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repositoryImpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;shoppingCartImpl&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;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If ID is nil, it's a new registration&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, consider the &lt;code&gt;GetByUserID&lt;/code&gt; method implementation of the repository. The repository can return an empty shopping cart if no data exists. Regardless of whether the aggregate exists, the right instance of the aggregate is returned, simplifying the repository interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;shoppingcart&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repositoryImpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetByUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findByUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&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;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Return an empty shopping cart aggregate if no data exists&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newEmptyShoppingCart&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c"&gt;// Create an aggregate using the data of the already saved shopping cart&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newShoppngCart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deep Module: The Importance of a Simple Interface
&lt;/h2&gt;

&lt;p&gt;Deep Module is a concept explained in the book &lt;a href="https://a.co/d/cjh92Je" rel="noopener noreferrer"&gt;A Philosophy of Software Design&lt;/a&gt; by a Stanford University professor. Deep Module refers to a module that has a simple and narrow interface on the surface but contains rich functionality and complexity internally. Conversely, modules with complex interfaces but little internal functionality are called Shallow Modules. Deep Modules excel in low cognitive load, high reusability, and ease of understanding. For example, Go language's &lt;code&gt;net/http&lt;/code&gt; package is a simple interface but encapsulates many features and complexities for implementing an HTTP server, making it easy to use. The file system of an OS is another example of a Deep Module. The concept of Deep Module has gained wide support in the IT industry in recent years.&lt;/p&gt;

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

&lt;p&gt;Source: &lt;a href="https://medium.com/@nakabonne/depth-of-module-f62dac3c2fdb" rel="noopener noreferrer"&gt;Depth of module&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Implementing repositories and aggregates according to DDD principles allows encapsulation of domain logic and enhances application maintainability. Determining aggregate boundaries requires deep consideration of domain and table design, but this is arguably one of the more enjoyable aspects of software development. In future developments, I aim to actively adopt DDD to explore more enjoyable application development.&lt;/p&gt;

&lt;p&gt;PS: Wishing everyone in Ishikawa Prefecture, Noto, and Wajima in Japan, who were affected by the earthquake on New Year's Day, a safe recovery.&lt;/p&gt;

</description>
      <category>ddd</category>
      <category>go</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I have built my first SaaS</title>
      <dc:creator>Yota Hamada</dc:creator>
      <pubDate>Sat, 18 Nov 2023 05:00:00 +0000</pubDate>
      <link>https://dev.to/yohamta/i-have-created-another-chatgpt-wrapper-service-391o</link>
      <guid>https://dev.to/yohamta/i-have-created-another-chatgpt-wrapper-service-391o</guid>
      <description>&lt;p&gt;Hello guys,&lt;br&gt;
I am a Golang developer working in a small company and also making an another product as a side hassle.&lt;/p&gt;

&lt;p&gt;I have just created a service that provide a chat GUI for a group of people who don't want their conversations to be learned by OpenAI for training their models.&lt;/p&gt;

&lt;p&gt;The service is available at the URL below.&lt;br&gt;
&lt;a href="https://chat.corpgpt.jp/"&gt;https://chat.corpgpt.jp/&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Currently it's just a simple app that allows user to manage their OpenAI API token and their organization members.&lt;/p&gt;

&lt;p&gt;It may be seemed boring to you. Indeed, it has nothing really interesting. However I think I try to use this simple service as an entry point to start a discussion with SMBs and enterprise companies that are eager to find an area where they can leverage the state-of-art generative AI technologies.&lt;/p&gt;

&lt;p&gt;Here is the technology stack.&lt;br&gt;
Backend: Go&lt;br&gt;
Frontend: TypeScript / React&lt;br&gt;
Infrastructure: Cloud Run on GCP&lt;/p&gt;

&lt;p&gt;Maybe it seems a bit weird that I use Go to create this kind of service, but that has been a great experience so far!&lt;/p&gt;

&lt;p&gt;If you are interested, please try it out and let me know your thoughts, ideas, advice, or anything.&lt;/p&gt;

&lt;p&gt;The news that OpenAI fired Sam Altman and CTO makes me a bit sad today, I hope the advance of the technology would continue and Sam and Greg will start an another great company or service tomorrow or soon.&lt;/p&gt;

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

</description>
      <category>go</category>
      <category>webdev</category>
      <category>indiehacker</category>
      <category>react</category>
    </item>
    <item>
      <title>Automate ChatGPT with Workflow Engine built with Go (example of sending goal seek results by email)</title>
      <dc:creator>Yota Hamada</dc:creator>
      <pubDate>Mon, 20 Mar 2023 09:52:47 +0000</pubDate>
      <link>https://dev.to/yohamta/automate-chatgpt-with-no-code-example-of-sending-goal-seek-results-by-email-2cgb</link>
      <guid>https://dev.to/yohamta/automate-chatgpt-with-no-code-example-of-sending-goal-seek-results-by-email-2cgb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction.
&lt;/h2&gt;

&lt;p&gt;This article shows how ChatGPT can be automated using Dagu, a free workflow engine. Specifically, we will show you how to use YAML definitions to run ChatGPT's API and automatically email the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a DAG workflow engine?
&lt;/h2&gt;

&lt;p&gt;A workflow engine is, simply put, a tool for automatically executing the tasks you do on your computer. It does not simply execute tasks in simple sequence, but can also specify dependencies between tasks and execute tasks in parallel at the same time. The following graph is a DAG (Directed acyclic graph).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F021dd4968a18-20230320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F021dd4968a18-20230320.png" alt="DAG"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Dagu is a free, open source DAG workflow engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DAGs can be created with no code (no programming required).&lt;/li&gt;
&lt;li&gt;All operations, including creating and running DAGs, can be done from a browser.&lt;/li&gt;
&lt;li&gt;Just install the command on your Mac and use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example of calling ChatGPT API and sending an answer by email
&lt;/h2&gt;

&lt;p&gt;The following is an example of calling ChatGPT's API and sending the results by email.&lt;/p&gt;

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

&lt;p&gt;What each step does is simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;call the ChatGPT API with the specified question&lt;/li&gt;
&lt;li&gt;extract the answers from the API response&lt;/li&gt;
&lt;li&gt;convert the response into an HTML email ready to be sent&lt;/li&gt;
&lt;li&gt;send the email.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result, you will receive an email that looks like this&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Steps to use ChatGPT to automate goal seek questions
&lt;/h2&gt;

&lt;p&gt;In this section, the Goal Seek prompt is used to automate Goal Seek. Note that any prompt can be automated in the same way by rewriting the contents of the DAG.&lt;/p&gt;

&lt;p&gt;The prompt used:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Goal] Goal to achieve.&lt;br&gt;
a[0,0]=[Goal]&lt;br&gt;
a[i,1-5]=5 ideas to achieve a[i,0].&lt;br&gt;
a[i+1,0]=a[i,1-5], which is the best idea to achieve a[i+1,0]=a[i,1-5]&lt;/p&gt;

&lt;p&gt;Use this to calculate up to i=0-5 and put the results in an HTML table with 5 rows and 5 columns, 25 cells in total.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installing Dagu
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Dagu can be installed using Homebrew.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;yohamta/tap/dagu
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can check if the installation is successful with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dagu version
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Launching Dagu Server
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Dagu server can be launched by the following command.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dagu server
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser. You should see a screen similar to the following.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F9a7bff83db11-20230320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F9a7bff83db11-20230320.png" alt="top"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a ChatGPT workflow.
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;click on the three-line menu on the left. The following is what you will see in my environment.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F6eb8db8e0dd7-20230320.png" alt="list"&gt;
&lt;/li&gt;
&lt;li&gt;press the 'NEW' button in the top right-hand corner and enter the name of the new DAG &lt;code&gt;chatgpt_goalseek_example&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;press the 'EDIT' button in the bottom right-hand corner.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Faa0378658739-20230320.png" alt="Edit"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;paste the following into the edit screen and save it ('SAVE' button).&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GOAL="Unique and interesting bullet-hell game."&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OPENAI_API_KEY="ChatGPT API Key"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MY_EMAIL="Recipient email address"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;FORMAT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;a[0,0]=[Goal]\n&lt;/span&gt;
      &lt;span class="s"&gt;Five ideas to achieve a[i,1-5]=a[i,0]. \n&lt;/span&gt;
      &lt;span class="s"&gt;The best idea to achieve a[i+1,0]=a[i,1-5]. \n&lt;/span&gt;
      &lt;span class="s"&gt;\n&lt;/span&gt;
      &lt;span class="s"&gt;Use this to calculate up to i=0-5 and put the results into a HTML table with 5 rows and 5 columns, 25 squares in total. \n&lt;/span&gt;

&lt;span class="na"&gt;smtp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;smtp.mailgun.org"&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;587"&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mailgun&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;username"&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mailgun&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password"&lt;/span&gt;

&lt;span class="na"&gt;steps&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;ask chatgpt&lt;/span&gt;
    &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$OPENAI_API_KEY"&lt;/span&gt;
          &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json"&lt;/span&gt;
        &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;{ "model": "gpt-3.5-turbo", "messages": [&lt;/span&gt;
              &lt;span class="s"&gt;{"role": "system", "content": "Act as a state-of-art chat AI. Please output the result in a table format with 5 rows and 5 columns. Format your reply in HTML code styled with beautiful CSS."},&lt;/span&gt;
              &lt;span class="s"&gt;{"role": "user", "content": "[Goal]${GOAL}\n${FORMAT}"}&lt;/span&gt;
            &lt;span class="s"&gt;]&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST https://api.openai.com/v1/chat/completions&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API_RESPONSE&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;get result&lt;/span&gt;
    &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jq&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;raw&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.choices[0].message.content"&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$API_RESPONSE"&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MESSAGE_CONTENT&lt;/span&gt;
    &lt;span class="na"&gt;depends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ask chatgpt&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;convert escaped unicode to plain HTML&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;input="${MESSAGE_CONTENT}"&lt;/span&gt;
      &lt;span class="s"&gt;unescaped=$(echo -e "$input" | sed 's/\\u003c/&amp;lt;/g' | sed 's/\\u003e/&amp;gt;/g')&lt;/span&gt;
      &lt;span class="s"&gt;echo "$unescaped"&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MESSAGE_CONTENT&lt;/span&gt;
    &lt;span class="na"&gt;depends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get result&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;send mail&lt;/span&gt;
    &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mail&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$MY_EMAIL"&lt;/span&gt;
        &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$MY_EMAIL"&lt;/span&gt;
        &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;goal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;seek&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;result"&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$MESSAGE_CONTENT"&lt;/span&gt;
    &lt;span class="na"&gt;depends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;convert escaped unicode to plain HTML&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up the ChatGPT API key and email sending server
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;set up the ChatGPT API key&lt;br&gt;
Rewrite the &lt;code&gt;ChatGPT API Key&lt;/code&gt; below with your ChatGPT API key.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OPENAI_API_KEY="ChatGPT API Key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set up a mail sending server&lt;br&gt;
Register with &lt;a href="https://www.mailgun.com/" rel="noopener noreferrer"&gt;Mailgun&lt;/a&gt; and you can send emails for free. Rewrite the following &lt;code&gt;Mailgun username&lt;/code&gt; and &lt;code&gt;Mailgun password&lt;/code&gt; with your Mailgun username and password. You can also use other email services.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;smtp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;smtp.mailgun.org"&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;587"&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mailgun&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Username"&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mailgun&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Password"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set the recipient email address&lt;br&gt;
Replace the following &lt;code&gt;Recipient email address&lt;/code&gt; with the email address to which you want to send the ChatGPT response.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MY_EMAIL="Recipient email address".&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Try it out and run it.
&lt;/h3&gt;

&lt;p&gt;Press the 'START' button in the top right-hand corner to run the DAG. In the text box, put in what you want to ask for in the Goal Seek prompt. For example, put in &lt;code&gt;The best and most interesting roguelike game idea&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Fb9d094da38eb-20230320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Fb9d094da38eb-20230320.png" alt="dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The execution status is displayed in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F3d31bbeb6982-20230320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F3d31bbeb6982-20230320.png" alt="status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If an error occurs, press the 'RETRY' button in the top right-hand corner; the error may occur if ChatGPT's API is heavy.&lt;/p&gt;

&lt;p&gt;If successful, you will see the following screen.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F047b862b46e4-20230320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F047b862b46e4-20230320.png" alt="success"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

&lt;p&gt;As you can see, the workflow engine allows you to automate ChatGPT API calls with no-code.&lt;/p&gt;

&lt;p&gt;If you always run similar prompts, it may be useful to define a DAG.&lt;/p&gt;

&lt;p&gt;You can do more interesting things by combining multiple inputs and outputs of ChatGPT's API. For example, ChatGPT responses could be passed to further APIs to improve or transform the results, and if defined as a DAG, it could be useful to combine multiple roles of ChatGPT, as the APIs can be called in parallel.&lt;/p&gt;

&lt;p&gt;I can only dream of the things we could do with it!&lt;/p&gt;

&lt;p&gt;Dagu is open source software that I am developing using the Go language.&lt;br&gt;
I would be happy if you could participate in its development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yohamta/dagu" rel="noopener noreferrer"&gt;https://github.com/yohamta/dagu&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>openai</category>
      <category>go</category>
      <category>nocode</category>
    </item>
  </channel>
</rss>
