Lewati ke isi

๐Ÿ›๏ธ Hexagonal Architecture dari Nol#

๐Ÿ“– Apa itu Hexagonal Architecture?#

Hexagonal Architecture (disebut juga Ports and Adapters) adalah cara mengorganisir kode aplikasi kita agar: - Business logic (logika bisnis) terpisah dari teknologi - Mudah diubah dan di-test - Tidak bergantung pada database, framework, atau API tertentu

๐Ÿค” Analogi Sederhana: Rumah dengan Banyak Pintu#

Bayangkan aplikasi kita adalah sebuah rumah (domain/business logic):

          ๐Ÿšช Pintu Depan (HTTP API)
               โ†“
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚                         โ”‚
    โ”‚    ๐Ÿ  RUMAH (DOMAIN)    โ”‚ โ† Inti aplikasi kita
    โ”‚   Business Logic        โ”‚
    โ”‚                         โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ†“              โ†“
   ๐Ÿšช Pintu Samping   ๐Ÿšช Pintu Belakang
   (CLI)             (Database)

Konsep Utama: - Rumah (Domain) = Business logic kita (aturan bisnis, entitas) - Pintu-pintu (Ports) = Interface/kontrak untuk masuk/keluar - Kunci Pintu (Adapters) = Implementasi konkret (PostgreSQL, FastAPI, dll)

Kenapa berbentuk Hexagon (Segi Enam)? Bukan karena harus 6 sisi, tapi untuk menunjukkan bahwa aplikasi bisa punya banyak pintu masuk dan keluar dari berbagai arah!


โ“ Mengapa Menggunakan Hexagonal Architecture?#

๐Ÿ”ด Masalah Tanpa Hexagonal Architecture#

Bayangkan kita bikin aplikasi toko online dengan cara biasa:

# โŒ Kode campur aduk (tanpa hexagonal)
from fastapi import FastAPI
from sqlalchemy import create_engine

app = FastAPI()

@app.post("/order")
def create_order(item: str, qty: int):
    # Business logic CAMPUR dengan teknologi!

    # 1. Validasi (business logic)
    if qty <= 0:
        return {"error": "Quantity harus positif"}

    # 2. Hitung harga (business logic)
    price = qty * 10000

    # 3. Simpan ke database (teknologi - PostgreSQL)
    engine = create_engine("postgresql://...")
    engine.execute(f"INSERT INTO orders VALUES ('{item}', {qty}, {price})")

    # 4. Kirim email (teknologi - SMTP)
    send_email_via_smtp("order@toko.com", f"Order baru: {item}")

    return {"success": True, "total": price}

๐Ÿšจ Masalahnya: 1. โŒ Sulit di-test - Harus punya database dan email server untuk testing 2. โŒ Sulit diubah - Mau ganti dari PostgreSQL ke MongoDB? Harus ubah semua kode! 3. โŒ Kode berantakan - Business logic (hitung harga) campur dengan database 4. โŒ Sulit dikembangkan tim - Semua orang harus nunggu setup database

โœ… Solusi: Dengan Hexagonal Architecture#

Kita pisahkan menjadi:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  INTERFACE (Pintu Masuk)                โ”‚
โ”‚  - FastAPI Controller                    โ”‚
โ”‚  - CLI Command                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  APPLICATION (Use Cases)                 โ”‚
โ”‚  - CreateOrderUseCase                    โ”‚
โ”‚  - SendOrderNotification                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  DOMAIN (Business Logic) ๐Ÿ               โ”‚
โ”‚  - Order Entity                          โ”‚
โ”‚  - Hitung total harga                    โ”‚
โ”‚  - Validasi quantity                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  INFRASTRUCTURE (Pintu Keluar)           โ”‚
โ”‚  - PostgreSQLRepository                  โ”‚
โ”‚  - EmailService                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽฏ Keuntungan: 1. โœ… Mudah di-test - Test business logic tanpa database! 2. โœ… Mudah diubah - Ganti PostgreSQL ke MongoDB? Tinggal ganti 1 file! 3. โœ… Kode rapi - Setiap layer punya tanggung jawab jelas 4. โœ… Tim bisa kerja paralel - Developer A bikin API, Developer B bikin business logic


๐Ÿ—๏ธ Cara Menggunakan Hexagonal Architecture#

๐Ÿ“ 4 Layer Utama#

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  1. INTERFACE (Delivery/Driving Adapters)       โ”‚
โ”‚     Cara user berinteraksi dengan aplikasi      โ”‚
โ”‚     - HTTP/REST API (FastAPI, Flask)            โ”‚
โ”‚     - CLI (Command Line)                        โ”‚
โ”‚     - GraphQL                                   โ”‚
โ”‚     - WebSocket                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“ (memanggil)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  2. APPLICATION (Use Cases)                     โ”‚
โ”‚     Apa yang bisa dilakukan aplikasi            โ”‚
โ”‚     - CreateOrderUseCase                        โ”‚
โ”‚     - GetOrderUseCase                           โ”‚
โ”‚     - UpdateOrderUseCase                        โ”‚
โ”‚     - DeleteOrderUseCase                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“ (menggunakan)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  3. DOMAIN (Business Logic) ๐Ÿ  INTI APLIKASI    โ”‚
โ”‚     Aturan bisnis & data penting                โ”‚
โ”‚     - Order (entity)                            โ”‚
โ”‚     - Product (entity)                          โ”‚
โ”‚     - Hitung diskon                             โ”‚
โ”‚     - Validasi stok                             โ”‚
โ”‚     - Repository Interface (kontrak)            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†‘ (diimplementasi oleh)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  4. INFRASTRUCTURE (Driven Adapters)            โ”‚
โ”‚     Implementasi teknologi eksternal            โ”‚
โ”‚     - PostgreSQLRepository                      โ”‚
โ”‚     - MongoDBRepository                         โ”‚
โ”‚     - EmailService (SMTP)                       โ”‚
โ”‚     - PaymentGateway (Midtrans)                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ’ก Contoh Lengkap: Aplikasi Toko Online#

1๏ธโƒฃ DOMAIN Layer (Inti Bisnis - Yang Paling Penting!)#

# domain/entities/order.py
from dataclasses import dataclass
from datetime import datetime

@dataclass
class Order:
    """Entity Order - Objek bisnis utama"""
    id: str
    product_name: str
    quantity: int
    price_per_item: int
    created_at: datetime

    def calculate_total(self) -> int:
        """Business logic: Hitung total harga"""
        return self.quantity * self.price_per_item

    def apply_discount(self, percentage: int) -> int:
        """Business logic: Hitung diskon"""
        total = self.calculate_total()
        discount = total * percentage / 100
        return total - discount

    def validate(self) -> bool:
        """Business logic: Validasi order"""
        if self.quantity <= 0:
            raise ValueError("Quantity harus lebih dari 0")
        if self.price_per_item <= 0:
            raise ValueError("Harga harus lebih dari 0")
        return True
# domain/repositories/order_repository.py
from abc import ABC, abstractmethod
from typing import List, Optional
from domain.entities.order import Order

class OrderRepository(ABC):
    """PORT (Interface) - Kontrak untuk simpan/ambil Order"""

    @abstractmethod
    def save(self, order: Order) -> Order:
        """Simpan order ke database"""
        pass

    @abstractmethod
    def find_by_id(self, order_id: str) -> Optional[Order]:
        """Cari order by ID"""
        pass

    @abstractmethod
    def find_all(self) -> List[Order]:
        """Ambil semua order"""
        pass

๐Ÿ”‘ Poin Penting Domain: - โœ… Tidak ada import dari FastAPI, SQLAlchemy, atau teknologi lain! - โœ… Pure Python - hanya business logic - โœ… Mudah di-test - tidak butuh database untuk test calculate_total()


2๏ธโƒฃ APPLICATION Layer (Use Cases)#

# application/usecases/create_order.py
from datetime import datetime
from domain.entities.order import Order
from domain.repositories.order_repository import OrderRepository

class CreateOrderUseCase:
    """Use Case: Buat order baru"""

    def __init__(self, order_repository: OrderRepository):
        # Dependency Injection - terima interface, bukan implementasi!
        self.order_repository = order_repository

    def execute(self, product_name: str, quantity: int, price: int) -> Order:
        """Jalankan use case"""

        # 1. Buat entity Order
        order = Order(
            id=self._generate_id(),
            product_name=product_name,
            quantity=quantity,
            price_per_item=price,
            created_at=datetime.now()
        )

        # 2. Validasi (business logic)
        order.validate()

        # 3. Simpan menggunakan repository (interface)
        saved_order = self.order_repository.save(order)

        # 4. Return
        return saved_order

    def _generate_id(self) -> str:
        import uuid
        return str(uuid.uuid4())

๐Ÿ”‘ Poin Penting Application: - โœ… Hanya depend pada Domain (import dari domain/) - โœ… Terima interface, bukan implementasi konkret - โœ… Satu use case = satu aksi bisnis


3๏ธโƒฃ INFRASTRUCTURE Layer (Implementasi Teknologi)#

# infrastructure/database/postgresql_order_repository.py
from typing import List, Optional
from sqlalchemy import create_engine, Column, String, Integer, DateTime
from sqlalchemy.orm import declarative_base, Session
from domain.entities.order import Order
from domain.repositories.order_repository import OrderRepository

Base = declarative_base()

class OrderModel(Base):
    """ORM Model untuk PostgreSQL"""
    __tablename__ = "orders"

    id = Column(String, primary_key=True)
    product_name = Column(String)
    quantity = Column(Integer)
    price_per_item = Column(Integer)
    created_at = Column(DateTime)

class PostgreSQLOrderRepository(OrderRepository):
    """ADAPTER - Implementasi OrderRepository untuk PostgreSQL"""

    def __init__(self, database_url: str):
        self.engine = create_engine(database_url)
        Base.metadata.create_all(self.engine)

    def save(self, order: Order) -> Order:
        """Simpan ke PostgreSQL"""
        with Session(self.engine) as session:
            order_model = OrderModel(
                id=order.id,
                product_name=order.product_name,
                quantity=order.quantity,
                price_per_item=order.price_per_item,
                created_at=order.created_at
            )
            session.add(order_model)
            session.commit()
        return order

    def find_by_id(self, order_id: str) -> Optional[Order]:
        """Cari dari PostgreSQL"""
        with Session(self.engine) as session:
            order_model = session.query(OrderModel).filter(
                OrderModel.id == order_id
            ).first()

            if not order_model:
                return None

            # Convert ORM model ke Domain entity
            return Order(
                id=order_model.id,
                product_name=order_model.product_name,
                quantity=order_model.quantity,
                price_per_item=order_model.price_per_item,
                created_at=order_model.created_at
            )

    def find_all(self) -> List[Order]:
        """Ambil semua dari PostgreSQL"""
        with Session(self.engine) as session:
            order_models = session.query(OrderModel).all()
            return [
                Order(
                    id=om.id,
                    product_name=om.product_name,
                    quantity=om.quantity,
                    price_per_item=om.price_per_item,
                    created_at=om.created_at
                )
                for om in order_models
            ]

๐Ÿ’ก Mau ganti ke MongoDB? Bikin adapter baru!

# infrastructure/database/mongodb_order_repository.py
from pymongo import MongoClient
from domain.repositories.order_repository import OrderRepository

class MongoDBOrderRepository(OrderRepository):
    """ADAPTER - Implementasi OrderRepository untuk MongoDB"""

    def __init__(self, connection_string: str):
        self.client = MongoClient(connection_string)
        self.db = self.client.orders_db
        self.collection = self.db.orders

    def save(self, order: Order) -> Order:
        """Simpan ke MongoDB"""
        self.collection.insert_one({
            "id": order.id,
            "product_name": order.product_name,
            "quantity": order.quantity,
            "price_per_item": order.price_per_item,
            "created_at": order.created_at
        })
        return order

    # ... implementasi lainnya

๐Ÿ”‘ Poin Penting Infrastructure: - โœ… Implementasi interface dari Domain - โœ… Semua teknologi (database, API eksternal, email) ada di sini - โœ… Ganti teknologi mudah - tinggal ganti adapter!


4๏ธโƒฃ INTERFACE Layer (API/CLI)#

# interface/http/order_controller.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from application.usecases.create_order import CreateOrderUseCase
from infrastructure.database.postgresql_order_repository import PostgreSQLOrderRepository

app = FastAPI()

# Setup dependency (bisa pakai Dependency Injection framework)
order_repository = PostgreSQLOrderRepository("postgresql://localhost/orders")
create_order_use_case = CreateOrderUseCase(order_repository)

class CreateOrderRequest(BaseModel):
    product_name: str
    quantity: int
    price: int

@app.post("/orders")
def create_order(request: CreateOrderRequest):
    """HTTP Endpoint untuk buat order"""
    try:
        # Panggil use case
        order = create_order_use_case.execute(
            product_name=request.product_name,
            quantity=request.quantity,
            price=request.price
        )

        # Return response
        return {
            "id": order.id,
            "product": order.product_name,
            "quantity": order.quantity,
            "total": order.calculate_total()
        }
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

CLI Alternative:

# interface/cli/order_cli.py
import click
from application.usecases.create_order import CreateOrderUseCase
from infrastructure.database.postgresql_order_repository import PostgreSQLOrderRepository

order_repository = PostgreSQLOrderRepository("postgresql://localhost/orders")
create_order_use_case = CreateOrderUseCase(order_repository)

@click.command()
@click.option('--product', prompt='Nama produk')
@click.option('--quantity', prompt='Jumlah', type=int)
@click.option('--price', prompt='Harga', type=int)
def create_order_cli(product: str, quantity: int, price: int):
    """CLI Command untuk buat order"""
    order = create_order_use_case.execute(product, quantity, price)
    click.echo(f"โœ… Order berhasil dibuat! ID: {order.id}")
    click.echo(f"Total: Rp {order.calculate_total():,}")

๐Ÿ”‘ Poin Penting Interface: - โœ… Hanya handle request/response - tidak ada business logic! - โœ… Panggil use case untuk eksekusi bisnis - โœ… Bisa punya banyak interface (HTTP, CLI, GraphQL) untuk use case yang sama


๐Ÿงช Testing Jadi Mudah!#

Test Domain (Tanpa Database!)#

# test/domain/test_order.py
from domain.entities.order import Order
from datetime import datetime

def test_calculate_total():
    """Test business logic tanpa database!"""
    order = Order(
        id="1",
        product_name="Laptop",
        quantity=2,
        price_per_item=5000000,
        created_at=datetime.now()
    )

    assert order.calculate_total() == 10000000  # 2 x 5 juta

def test_apply_discount():
    order = Order(
        id="1",
        product_name="Laptop",
        quantity=2,
        price_per_item=5000000,
        created_at=datetime.now()
    )

    # Diskon 10%
    final_price = order.apply_discount(10)
    assert final_price == 9000000  # 10 juta - 10%

Test Use Case dengan Mock#

# test/application/test_create_order.py
from unittest.mock import Mock
from application.usecases.create_order import CreateOrderUseCase

def test_create_order():
    """Test use case dengan mock repository"""
    # Mock repository (fake, bukan database beneran)
    mock_repo = Mock()
    mock_repo.save.return_value = Mock(id="123")

    # Test use case
    use_case = CreateOrderUseCase(mock_repo)
    order = use_case.execute("Laptop", 2, 5000000)

    # Verify
    assert mock_repo.save.called
    assert order is not None

๐Ÿ“Š Struktur Folder yang Benar#

src/
โ”œโ”€โ”€ domain/                          # ๐Ÿ  INTI (Paling Penting!)
โ”‚   โ”œโ”€โ”€ entities/                    # Objek bisnis
โ”‚   โ”‚   โ”œโ”€โ”€ order.py
โ”‚   โ”‚   โ”œโ”€โ”€ product.py
โ”‚   โ”‚   โ””โ”€โ”€ user.py
โ”‚   โ”œโ”€โ”€ value_objects/               # Konsep immutable
โ”‚   โ”‚   โ”œโ”€โ”€ email.py
โ”‚   โ”‚   โ””โ”€โ”€ money.py
โ”‚   โ”œโ”€โ”€ services/                    # Logic bisnis kompleks
โ”‚   โ”‚   โ””โ”€โ”€ pricing_service.py
โ”‚   โ””โ”€โ”€ repositories/                # Interface (kontrak)
โ”‚       โ”œโ”€โ”€ order_repository.py
โ”‚       โ””โ”€โ”€ product_repository.py
โ”‚
โ”œโ”€โ”€ application/                     # ๐ŸŽฏ Use Cases
โ”‚   โ”œโ”€โ”€ usecases/
โ”‚   โ”‚   โ”œโ”€โ”€ create_order.py
โ”‚   โ”‚   โ”œโ”€โ”€ get_order.py
โ”‚   โ”‚   โ””โ”€โ”€ update_order.py
โ”‚   โ”œโ”€โ”€ dto/                         # Data transfer objects
โ”‚   โ”‚   โ”œโ”€โ”€ order_dto.py
โ”‚   โ”‚   โ””โ”€โ”€ product_dto.py
โ”‚   โ””โ”€โ”€ services/                    # Application services
โ”‚       โ””โ”€โ”€ order_service.py
โ”‚
โ”œโ”€โ”€ infrastructure/                  # ๐Ÿ”ง Implementasi Teknologi
โ”‚   โ”œโ”€โ”€ database/
โ”‚   โ”‚   โ”œโ”€โ”€ postgresql_order_repository.py
โ”‚   โ”‚   โ””โ”€โ”€ mongodb_product_repository.py
โ”‚   โ”œโ”€โ”€ external/
โ”‚   โ”‚   โ”œโ”€โ”€ midtrans_payment.py
โ”‚   โ”‚   โ””โ”€โ”€ smtp_email_service.py
โ”‚   โ””โ”€โ”€ ai/
โ”‚       โ””โ”€โ”€ openai_service.py
โ”‚
โ””โ”€โ”€ interface/                       # ๐ŸŒ API/CLI
    โ”œโ”€โ”€ http/
    โ”‚   โ”œโ”€โ”€ order_controller.py
    โ”‚   โ””โ”€โ”€ product_controller.py
    โ””โ”€โ”€ cli/
        โ””โ”€โ”€ order_cli.py

๐ŸŽฏ Aturan Emas Hexagonal Architecture#

โœ… BOLEH:#

interface โ†’ application โ†’ domain
              โ†“
         infrastructure
  1. โœ… Interface boleh import Application
  2. โœ… Application boleh import Domain
  3. โœ… Infrastructure boleh import Domain (untuk implement interface)
  4. โœ… Infrastructure boleh import Application (untuk implement ports)

โŒ TIDAK BOLEH:#

  1. โŒ Domain TIDAK BOLEH import Application
  2. โŒ Domain TIDAK BOLEH import Infrastructure
  3. โŒ Domain TIDAK BOLEH import Interface
  4. โŒ Application TIDAK BOLEH import Infrastructure
  5. โŒ Application TIDAK BOLEH import Interface

Contoh Salah:

# โŒ SALAH! Domain tidak boleh import SQLAlchemy
# domain/entities/order.py
from sqlalchemy import Column, Integer  # โŒ TIDAK BOLEH!

class Order:
    id = Column(Integer)  # โŒ TIDAK BOLEH!

Contoh Benar:

# โœ… BENAR! Domain pure Python
# domain/entities/order.py
from dataclasses import dataclass

@dataclass
class Order:  # โœ… Pure Python!
    id: str
    quantity: int

๐Ÿš€ Langkah-langkah Implementasi#

Step 1: Mulai dari Domain (INTI)#

  1. Identifikasi objek bisnis (Entity)
  2. Order
  3. Product
  4. User

  5. Tulis business logic

  6. Validasi
  7. Perhitungan
  8. Aturan bisnis

  9. Buat interface repository

  10. Kontrak untuk save/find data

Step 2: Buat Use Cases (Application)#

  1. Identifikasi aksi user
  2. Create order
  3. Cancel order
  4. Get order

  5. Implementasi use case

  6. Panggil domain entity
  7. Gunakan repository interface

Step 3: Implementasi Teknologi (Infrastructure)#

  1. Pilih database (PostgreSQL/MongoDB/dll)
  2. Implementasi repository interface
  3. Setup connection
  4. Implementasi external services

Step 4: Buat Interface (API/CLI)#

  1. Pilih delivery mechanism (FastAPI/Flask/CLI)
  2. Buat controller/handler
  3. Panggil use case
  4. Return response

๐Ÿ’ก Tips untuk Pemula#

1. Mulai Sederhana#

Jangan langsung kompleks! Mulai dengan 1 entity, 1 use case, 1 endpoint.

2. Domain Dulu, Teknologi Kemudian#

Pikirkan business logic dulu, baru teknologi (database, API, dll).

3. Test Business Logic Tanpa Database#

Ini keuntungan terbesar! Test tanpa setup database.

4. Jangan Takut Refactor#

Ganti PostgreSQL ke MongoDB? Tinggal ganti 1 file di infrastructure!

5. Konsisten dengan Aturan#

Domain tidak boleh tahu tentang FastAPI, SQLAlchemy, atau teknologi lain!


๐Ÿ“š Kesimpulan#

Hexagonal Architecture = Pisahkan Business Logic dari Teknologi

  • Domain = Aturan bisnis (inti aplikasi) ๐Ÿ 
  • Application = Apa yang bisa dilakukan aplikasi ๐ŸŽฏ
  • Infrastructure = Implementasi teknologi (database, email, dll) ๐Ÿ”ง
  • Interface = Cara user akses aplikasi (API, CLI, dll) ๐ŸŒ

Keuntungan: - โœ… Mudah di-test - โœ… Mudah diubah - โœ… Kode rapi dan terorganisir - โœ… Tim bisa kerja paralel - โœ… Independent dari framework/database

Ingat: Domain adalah RAJA! Semua layer lain melayani domain! ๐Ÿ‘‘