<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>english &#8211; Selman Tunc</title>
	<atom:link href="https://selmantunc.com.tr/category/english/feed/" rel="self" type="application/rss+xml" />
	<link>https://selmantunc.com.tr</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 04:19:43 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://selmantunc.com.tr/wp-content/uploads/2023/07/cropped-tumblr_inline_oglumuMbgO1tyldvk_540-150x150-1-32x32.jpg</url>
	<title>english &#8211; Selman Tunc</title>
	<link>https://selmantunc.com.tr</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>The Domain Concept for Hexagonal Architecture in Go (Golang)</title>
		<link>https://selmantunc.com.tr/golang/the-domain-concept-for-hexagonal-architecture-in-go-golang/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Mon, 13 Apr 2026 04:19:43 +0000</pubDate>
				<category><![CDATA[english]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[software]]></category>
		<guid isPermaLink="false">https://selmantunc.com.tr/?p=3541</guid>

					<description><![CDATA[Here is the English version of the Go (Golang) Hexagonal Architecture example, with the same structure and explanations. 📁 Folder Structure hexagonal-go/ ├── go.mod ├── cmd/ │ └── api/ │&#8230;]]></description>
										<content:encoded><![CDATA[<p>Here is the <strong>English version</strong> of the Go (Golang) Hexagonal Architecture example, with the same structure and explanations.</p>
<hr />
<h1>📁 Folder Structure</h1>
<pre><code class="lang-text language-text text">hexagonal-go/
├── go.mod
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    ├── domain/
    │   └── order/
    │       ├── money.go
    │       ├── order.go
    │       ├── order_id.go
    │       ├── order_status.go
    │       └── repository.go
    ├── application/
    │   └── order_service.go
    └── adapters/
        ├── http/
        │   └── order_handler.go
        └── persistence/
            └── memory/
                └── order_repository.go</code></pre>
<hr />
<h1>1) <code>go.mod</code></h1>
<pre><code class="lang-go language-go go">module example.com/hexagonal-go

go 1.22</code></pre>
<hr />
<h1>2) Domain Layer</h1>
<h3><code>internal/domain/order/order_id.go</code></h3>
<pre><code class="lang-go language-go go">package order

type OrderID string</code></pre>
<h3><code>internal/domain/order/order_status.go</code></h3>
<pre><code class="lang-go language-go go">package order

type OrderStatus string

const (
    OrderStatusCreated   OrderStatus = &quot;CREATED&quot;
    OrderStatusCompleted OrderStatus = &quot;COMPLETED&quot;
)</code></pre>
<h3><code>internal/domain/order/money.go</code></h3>
<pre><code class="lang-go language-go go">package order

import &quot;fmt&quot;

type Money struct {
    Cents int64
}

func NewMoney(cents int64) (Money, error) {
    if cents &lt; 0 {
        return Money{}, fmt.Errorf(&quot;money cannot be negative&quot;)
    }
    return Money{Cents: cents}, nil
}

func (m Money) Add(other Money) Money {
    return Money{Cents: m.Cents + other.Cents}
}</code></pre>
<h3><code>internal/domain/order/order.go</code></h3>
<pre><code class="lang-go language-go go">package order

import &quot;fmt&quot;

type OrderItem struct {
    ProductID string
    Quantity  int
    UnitPrice Money
}

type Order struct {
    id     OrderID
    items  []OrderItem
    status OrderStatus
}

func NewOrder(id OrderID) *Order {
    return &amp;Order{
        id:     id,
        items:  make([]OrderItem, 0),
        status: OrderStatusCreated,
    }
}

func (o *Order) ID() OrderID {
    return o.id
}

func (o *Order) Status() OrderStatus {
    return o.status
}

func (o *Order) Items() []OrderItem {
    copied := make([]OrderItem, len(o.items))
    copy(copied, o.items)
    return copied
}

func (o *Order) AddItem(productID string, quantity int, unitPrice Money) error {
    if o.status != OrderStatusCreated {
        return fmt.Errorf(&quot;order cannot be modified after completion&quot;)
    }
    if productID == &quot;&quot; {
        return fmt.Errorf(&quot;product id cannot be empty&quot;)
    }
    if quantity &lt;= 0 {
        return fmt.Errorf(&quot;quantity must be greater than zero&quot;)
    }

    o.items = append(o.items, OrderItem{
        ProductID: productID,
        Quantity:  quantity,
        UnitPrice: unitPrice,
    })

    return nil
}

func (o *Order) Complete() error {
    if len(o.items) == 0 {
        return fmt.Errorf(&quot;order must have at least one item&quot;)
    }
    o.status = OrderStatusCompleted
    return nil
}

func (o *Order) Total() Money {
    total := Money{Cents: 0}
    for _, item := range o.items {
        total = total.Add(Money{Cents: item.UnitPrice.Cents * int64(item.Quantity)})
    }
    return total
}</code></pre>
<h3><code>internal/domain/order/repository.go</code></h3>
<pre><code class="lang-go language-go go">package order

import &quot;context&quot;

type Repository interface {
    Save(ctx context.Context, order *Order) error
    FindByID(ctx context.Context, id OrderID) (*Order, error)
}</code></pre>
<hr />
<h1>3) Application Layer</h1>
<h3><code>internal/application/order_service.go</code></h3>
<pre><code class="lang-go language-go go">package application

import (
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;time&quot;

    &quot;example.com/hexagonal-go/internal/domain/order&quot;
)

type OrderService struct {
    repo order.Repository
}

func NewOrderService(repo order.Repository) *OrderService {
    return &amp;OrderService{repo: repo}
}

func (s *OrderService) CreateOrder(ctx context.Context) (*order.Order, error) {
    id := order.OrderID(fmt.Sprintf(&quot;ord_%d&quot;, time.Now().UnixNano()))
    newOrder := order.NewOrder(id)

    if err := s.repo.Save(ctx, newOrder); err != nil {
        return nil, err
    }

    return newOrder, nil
}

func (s *OrderService) AddItem(ctx context.Context, orderID order.OrderID, productID string, quantity int, priceCents int64) (*order.Order, error) {
    o, err := s.repo.FindByID(ctx, orderID)
    if err != nil {
        return nil, err
    }

    price, err := order.NewMoney(priceCents)
    if err != nil {
        return nil, err
    }

    if err := o.AddItem(productID, quantity, price); err != nil {
        return nil, err
    }

    if err := s.repo.Save(ctx, o); err != nil {
        return nil, err
    }

    return o, nil
}

func (s *OrderService) CompleteOrder(ctx context.Context, orderID order.OrderID) (*order.Order, error) {
    o, err := s.repo.FindByID(ctx, orderID)
    if err != nil {
        return nil, err
    }

    if err := o.Complete(); err != nil {
        return nil, err
    }

    if err := s.repo.Save(ctx, o); err != nil {
        return nil, err
    }

    return o, nil
}

func (s *OrderService) GetOrder(ctx context.Context, orderID order.OrderID) (*order.Order, error) {
    return s.repo.FindByID(ctx, orderID)
}</code></pre>
<hr />
<h1>4) Persistence Adapter (In-Memory)</h1>
<h3><code>internal/adapters/persistence/memory/order_repository.go</code></h3>
<pre><code class="lang-go language-go go">package memory

import (
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;sync&quot;

    &quot;example.com/hexagonal-go/internal/domain/order&quot;
)

type OrderRepository struct {
    mu     sync.RWMutex
    orders map[order.OrderID]*order.Order
}

func NewOrderRepository() *OrderRepository {
    return &amp;OrderRepository{
        orders: make(map[order.OrderID]*order.Order),
    }
}

func (r *OrderRepository) Save(ctx context.Context, o *order.Order) error {
    r.mu.Lock()
    defer r.mu.Unlock()

    r.orders[o.ID()] = o
    return nil
}

func (r *OrderRepository) FindByID(ctx context.Context, id order.OrderID) (*order.Order, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()

    o, ok := r.orders[id]
    if !ok {
        return nil, fmt.Errorf(&quot;order not found: %s&quot;, id)
    }

    return o, nil
}</code></pre>
<hr />
<h1>5) HTTP Adapter</h1>
<h3><code>internal/adapters/http/order_handler.go</code></h3>
<pre><code class="lang-go language-go go">package httpadapter

import (
    &quot;encoding/json&quot;
    &quot;net/http&quot;
    &quot;strings&quot;

    &quot;example.com/hexagonal-go/internal/application&quot;
    &quot;example.com/hexagonal-go/internal/domain/order&quot;
)

type OrderHandler struct {
    service *application.OrderService
}

func NewOrderHandler(service *application.OrderService) *OrderHandler {
    return &amp;OrderHandler{service: service}
}

func (h *OrderHandler) RegisterRoutes(mux *http.ServeMux) {
    mux.HandleFunc(&quot;POST /orders&quot;, h.createOrder)
    mux.HandleFunc(&quot;GET /orders/&quot;, h.getOrder)
    mux.HandleFunc(&quot;POST /orders/{id}/items&quot;, h.addItem)
    mux.HandleFunc(&quot;POST /orders/{id}/complete&quot;, h.completeOrder)
}

func (h *OrderHandler) createOrder(w http.ResponseWriter, r *http.Request) {
    o, err := h.service.CreateOrder(r.Context())
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    respondJSON(w, http.StatusCreated, map[string]any{
        &quot;id&quot;:     o.ID(),
        &quot;status&quot;: o.Status(),
        &quot;total&quot;:  o.Total().Cents,
    })
}

func (h *OrderHandler) getOrder(w http.ResponseWriter, r *http.Request) {
    id := extractID(r.URL.Path)
    o, err := h.service.GetOrder(r.Context(), order.OrderID(id))
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }

    respondJSON(w, http.StatusOK, map[string]any{
        &quot;id&quot;:     o.ID(),
        &quot;status&quot;: o.Status(),
        &quot;items&quot;:  o.Items(),
        &quot;total&quot;:  o.Total().Cents,
    })
}

func (h *OrderHandler) addItem(w http.ResponseWriter, r *http.Request) {
    id := extractID(r.URL.Path)

    var req struct {
        ProductID string `json:&quot;product_id&quot;`
        Quantity  int    `json:&quot;quantity&quot;`
        PriceCents int64  `json:&quot;price_cents&quot;`
    }

    if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {
        http.Error(w, &quot;invalid json&quot;, http.StatusBadRequest)
        return
    }

    o, err := h.service.AddItem(r.Context(), order.OrderID(id), req.ProductID, req.Quantity, req.PriceCents)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    respondJSON(w, http.StatusOK, map[string]any{
        &quot;id&quot;:     o.ID(),
        &quot;status&quot;: o.Status(),
        &quot;total&quot;:  o.Total().Cents,
    })
}

func (h *OrderHandler) completeOrder(w http.ResponseWriter, r *http.Request) {
    id := extractID(r.URL.Path)
    o, err := h.service.CompleteOrder(r.Context(), order.OrderID(id))
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    respondJSON(w, http.StatusOK, map[string]any{
        &quot;id&quot;:     o.ID(),
        &quot;status&quot;: o.Status(),
        &quot;total&quot;:  o.Total().Cents,
    })
}

func extractID(path string) string {
    parts := strings.Split(strings.Trim(path, &quot;/&quot;), &quot;/&quot;)
    if len(parts) &gt;= 2 {
        return parts[1]
    }
    return &quot;&quot;
}

func respondJSON(w http.ResponseWriter, status int, payload any) {
    w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
    w.WriteHeader(status)
    _ = json.NewEncoder(w).Encode(payload)
}</code></pre>
<hr />
<h1>6) Application Entry Point</h1>
<h3><code>cmd/api/main.go</code></h3>
<pre><code class="lang-go language-go go">package main

import (
    &quot;log&quot;
    &quot;net/http&quot;

    httpadapter &quot;example.com/hexagonal-go/internal/adapters/http&quot;
    &quot;example.com/hexagonal-go/internal/adapters/persistence/memory&quot;
    &quot;example.com/hexagonal-go/internal/application&quot;
)

func main() {
    repo := memory.NewOrderRepository()
    service := application.NewOrderService(repo)
    handler := httpadapter.NewOrderHandler(service)

    mux := http.NewServeMux()
    handler.RegisterRoutes(mux)

    log.Println(&quot;server started on :8080&quot;)
    if err := http.ListenAndServe(&quot;:8080&quot;, mux); err != nil {
        log.Fatal(err)
    }
}</code></pre>
<hr />
<h1>7) How It Works</h1>
<ul>
<li><strong>HTTP adapter</strong> receives the request</li>
<li><strong>Application service</strong> executes the use case</li>
<li><strong>Domain</strong> applies business rules</li>
<li><strong>Repository (port)</strong> is defined in the domain</li>
<li><strong>Adapter (memory)</strong> implements it</li>
</ul>
<p>👉 The domain layer:</p>
<ul>
<li>does NOT know <code>net/http</code></li>
<li>does NOT know databases</li>
<li>does NOT know frameworks</li>
</ul>
<hr />
<h1>8) Simple Test Example</h1>
<h3><code>internal/domain/order/order_test.go</code></h3>
<pre><code class="lang-go language-go go">package order

import &quot;testing&quot;

func TestOrderCompleteWithoutItems(t *testing.T) {
    o := NewOrder(&quot;1&quot;)

    if err := o.Complete(); err == nil {
        t.Fatal(&quot;expected error when completing empty order&quot;)
    }
}

func TestOrderTotal(t *testing.T) {
    o := NewOrder(&quot;1&quot;)
    price, _ := NewMoney(500)

    if err := o.AddItem(&quot;p1&quot;, 2, price); err != nil {
        t.Fatal(err)
    }

    if o.Total().Cents != 1000 {
        t.Fatalf(&quot;expected 1000, got %d&quot;, o.Total().Cents)
    }
}</code></pre>
<hr />
<h1>9) Run the Project</h1>
<pre><code class="lang-bash language-bash bash">go run ./cmd/api</code></pre>
<p>Example requests:</p>
<pre><code class="lang-bash language-bash bash">curl -X POST localhost:8080/orders</code></pre>
<pre><code class="lang-bash language-bash bash">curl -X POST localhost:8080/orders/ord_xxx/items \
  -H 'Content-Type: application/json' \
  -d '{&quot;product_id&quot;:&quot;p1&quot;,&quot;quantity&quot;:2,&quot;price_cents&quot;:500}'</code></pre>
<pre><code class="lang-bash language-bash bash">curl -X POST localhost:8080/orders/ord_xxx/complete</code></pre>
<hr />]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
