← Blog
tutorials

Arkitettura Eżagonali | Gwida Prattika għal Applikazzjonijiet Moderni

Tgħallem x'inhi l-arkitettura eżagonali, għaliex hija importanti, u kif timplimentaha. Gwida kompleta b'eżempji reali biex tibni software mantenibbli u testabbli.

Ryveris Team ·
Arkitettura Eżagonali | Gwida Prattika għal Applikazzjonijiet Moderni

L-arkitettura eżagonali hija pattern ta’ disinn tas-software li tiżola l-loġika ewlenija tan-negozju tiegħek minn sistemi esterni bħal databases, APIs, u interfaces tal-utent. L-idea hija sempliċi: il-kodiċi tad-domain tiegħek qatt m’għandu jiddependi fuq l-infrastruttura. L-infrastruttura tiddependi fuq id-domain.

Il-pattern ġiet introdotta minn Alistair Cockburn fl-2005. Tista’ wkoll tismagħha tissejjaħ “Ports and Adapters.” Iż-żewġ ismijiet jiddeskrivu l-istess kunċett.

Għaliex Teżisti

Il-biċċa l-kbira tal-applikazzjonijiet jibdew bl-istess mod. Il-loġika tan-negozju titħallat mal-queries tad-database, HTTP handlers, u sejħiet ta’ servizzi ta’ parti terza. Taħdem fil-bidu. Imbagħad il-codebase tikber, u tliet affarijiet iseħħu:

  1. L-ittestjar isir ta’ uġigħ. Ma tistax tittestja r-regoli tan-negozju mingħajr ma tqajjem database jew timmokka nofs il-framework.
  2. Li tibdel l-infrastruttura huwa riskjuż. Li tibdel fornitur tal-pagamenti jew timmigra minn REST għal GraphQL ifisser li terġa’ tikteb il-loġika tan-negozju.
  3. Il-kodiċi huwa diffiċli biex tirraġuna dwaru. Ħadd ma jista’ jgħid fejn jispiċċa d-domain u fejn tibda l-infrastruttura.

L-arkitettura eżagonali ssolvi t-tliet problemi kollha billi tisforza regola waħda: id-dipendenzi dejjem jippuntaw ‘il ġewwa.

Il-Kunċetti Ewlenin

Aħseb fl-applikazzjoni tiegħek bħala tliet saffi konċentriċi.

Id-Domain (Ċentru)

Din hija l-loġika tan-negozju tiegħek. Funzjonijiet puri, entitajiet, value objects, u servizzi tad-domain. Għandha żero dipendenzi fuq frameworks, databases, jew libreriji esterni. Titkellem il-lingwa tagħha stess.

Għal sistema ta’ e-commerce, dan is-saff fih kunċetti bħal Order, Product, PricingRule, u InventoryPolicy. Dawn l-oġġetti ma jafu xejn dwar SQL, HTTP, jew JSON.

Ports (Saff tan-Nofs)

Il-Ports huma interfaces li jiddefinixxu kif id-dinja ta’ barra tinteraġixxi mad-domain. Huma kuntratti, mhux implimentazzjonijiet.

Hemm żewġ tipi:

  • Inbound ports (driving). Jiddefinixxu x’tista’ tagħmel l-applikazzjoni. Aħseb fihom bħala use cases. “Agħmel ordni.” “Ikkanċella abbonament.” “Iġġenera fattura.”
  • Outbound ports (driven). Jiddefinixxu x’teħtieġ l-applikazzjoni mid-dinja ta’ barra. “Issejvja ordni.” “Ibgħat notifika.” “Iġbed ir-rata tal-kambju.”

Il-Ports huma parti mis-saff tad-domain. Huma miktuba bil-lingwa tad-domain, mhux bil-lingwa tal-infrastruttura. Tikteb OrderRepository, mhux PostgresOrderDAO.

Adapters (Saff ta’ Barra)

L-Adapters huma l-implimentazzjonijiet konkreti li jgħaqqdu d-dinja reali mal-ports tiegħek.

  • Inbound adapters jittraduċu talbiet esterni f’sejħiet tad-domain. REST controller, GraphQL resolver, kmand CLI, consumer ta’ message queue. Dawn kollha huma inbound adapters.
  • Outbound adapters jimplimentaw l-interfaces tal-outbound port. Repository ta’ PostgreSQL, email sender ta’ SMTP, payment gateway ta’ Stripe. Dawn kollha huma outbound adapters.

L-insight ewlieni: l-adapters jiddependu fuq il-ports. Il-ports qatt ma jiddependu fuq l-adapters. Dan huwa dak li jagħmel il-pattern kollha taħdem.

Eżempju Konkret

Ejja ngħidu li qiegħed tibni feature ta’ pjazzament ta’ ordni. Hawn kif is-saffi jinqasmu.

Domain (entitajiet u regoli):

// domain/Order.ts
class Order {
  readonly id: string;
  readonly items: OrderItem[];
  readonly status: OrderStatus;

  calculateTotal(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.price.multiply(item.quantity)),
      Money.zero()
    );
  }

  confirm(): Order {
    if (this.items.length === 0) {
      throw new EmptyOrderError();
    }
    return new Order({ ...this, status: "confirmed" });
  }
}

Inbound port (interface tal-use case):

// ports/inbound/PlaceOrder.ts
interface PlaceOrder {
  execute(command: PlaceOrderCommand): Promise<OrderConfirmation>;
}

Outbound ports (dak li l-use case jeħtieġ):

// ports/outbound/OrderRepository.ts
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order | null>;
}

// ports/outbound/PaymentGateway.ts
interface PaymentGateway {
  charge(amount: Money, method: PaymentMethod): Promise<PaymentResult>;
}

// ports/outbound/NotificationSender.ts
interface NotificationSender {
  sendOrderConfirmation(order: Order): Promise<void>;
}

Servizz tal-applikazzjoni (jimplimenta l-inbound port):

// application/PlaceOrderService.ts
class PlaceOrderService implements PlaceOrder {
  constructor(
    private orders: OrderRepository,
    private payments: PaymentGateway,
    private notifications: NotificationSender
  ) {}

  async execute(command: PlaceOrderCommand): Promise<OrderConfirmation> {
    const order = Order.create(command.items);
    const confirmed = order.confirm();

    const payment = await this.payments.charge(
      confirmed.calculateTotal(),
      command.paymentMethod
    );

    if (!payment.successful) {
      throw new PaymentFailedError(payment.reason);
    }

    await this.orders.save(confirmed);
    await this.notifications.sendOrderConfirmation(confirmed);

    return OrderConfirmation.from(confirmed, payment);
  }
}

Adapters (infrastruttura):

// adapters/inbound/OrderController.ts
class OrderController {
  constructor(private placeOrder: PlaceOrder) {}

  async handlePost(req: Request): Promise<Response> {
    const command = PlaceOrderCommand.fromRequest(req.body);
    const confirmation = await this.placeOrder.execute(command);
    return Response.json(confirmation.toResponse(), { status: 201 });
  }
}

// adapters/outbound/PostgresOrderRepository.ts
class PostgresOrderRepository implements OrderRepository {
  async save(order: Order): Promise<void> {
    await this.db.query(
      "INSERT INTO orders (id, items, status) VALUES ($1, $2, $3)",
      [order.id, JSON.stringify(order.items), order.status]
    );
  }

  async findById(id: string): Promise<Order | null> {
    const row = await this.db.query("SELECT * FROM orders WHERE id = $1", [id]);
    return row ? Order.fromPersistence(row) : null;
  }
}

Innota kif PlaceOrderService qatt ma jsemmi PostgreSQL, HTTP, jew xi framework. Jaħdem purament b’kunċetti tad-domain u interfaces tal-port.

Ir-Regola tad-Dipendenza

Din hija l-aktar regola importanti. Jekk ma tieħu xejn aktar minn dan l-artikolu, ħu dan:

Id-dipendenzi tas-source code iridu dejjem jippuntaw ‘il ġewwa, lejn id-domain.

  • L-Adapters jiddependu fuq il-ports. Qatt il-maqlub.
  • Il-Ports jiddependu fuq l-entitajiet tad-domain. Qatt fuq l-adapters.
  • Id-domain ma jiddependix fuq xejn estern.

Dan jiġi infurzat permezz ta’ dependency injection. Il-composition root tiegħek (il-punt tad-dħul tal-applikazzjoni) tgħaqqad kollox flimkien:

// main.ts (composition root)
const orderRepo = new PostgresOrderRepository(db);
const payments = new StripePaymentGateway(stripeClient);
const notifications = new EmailNotificationSender(mailer);

const placeOrder = new PlaceOrderService(orderRepo, payments, notifications);
const controller = new OrderController(placeOrder);

Is-saff tad-domain qatt ma jimporta mis-saff tal-adapters. Jekk tara import minn adapters/ ġewwa domain/ jew ports/, xi ħaġa hija ħażina.

Struttura tal-Proġett

Struttura ta’ folders nadifa tagħmel il-konfini viżibbli:

src/
  domain/
    Order.ts
    OrderItem.ts
    Money.ts
    errors/
      EmptyOrderError.ts
      PaymentFailedError.ts
  ports/
    inbound/
      PlaceOrder.ts
      CancelOrder.ts
    outbound/
      OrderRepository.ts
      PaymentGateway.ts
      NotificationSender.ts
  application/
    PlaceOrderService.ts
    CancelOrderService.ts
  adapters/
    inbound/
      http/
        OrderController.ts
      cli/
        PlaceOrderCommand.ts
    outbound/
      persistence/
        PostgresOrderRepository.ts
      payment/
        StripePaymentGateway.ts
      notification/
        EmailNotificationSender.ts
  main.ts

Kull żviluppatur fit-tim jista’ jħares lejn din is-siġra u jifhem fejn iqiegħed kodiċi ġdid.

Benefiċċji tal-Ittestjar

Hawn fejn l-arkitettura eżagonali tabilħaqq tħallas.

Ittestjar tal-unit tad-domain:

test("order calculates total correctly", () => {
  const order = Order.create([
    { product: "Widget", price: Money.eur(10), quantity: 3 },
    { product: "Gadget", price: Money.eur(25), quantity: 1 },
  ]);

  expect(order.calculateTotal()).toEqual(Money.eur(55));
});

L-ebda database. L-ebda HTTP server. L-ebda mocking framework. Loġika pura, testijiet puri.

Ittestjar ta’ use cases b’fake adapters:

test("place order charges payment and saves", async () => {
  const orders = new InMemoryOrderRepository();
  const payments = new FakePaymentGateway({ alwaysSucceeds: true });
  const notifications = new FakeNotificationSender();

  const service = new PlaceOrderService(orders, payments, notifications);

  const result = await service.execute({
    items: [{ product: "Widget", price: 10, quantity: 2 }],
    paymentMethod: { type: "card", token: "tok_test" },
  });

  expect(result.status).toBe("confirmed");
  expect(orders.savedOrders).toHaveLength(1);
  expect(payments.charges).toHaveLength(1);
  expect(notifications.sent).toHaveLength(1);
});

Tittestja l-use case sħiħ mingħajr ma tmiss infrastruttura reali. Il-fake adapters jimplimentaw l-istess interfaces tal-port, għalhekk huma intercambjabbli ma’ dawk reali.

Integration tests biss għall-adapters:

test("PostgresOrderRepository saves and retrieves", async () => {
  const repo = new PostgresOrderRepository(testDb);
  const order = Order.create([
    { product: "Widget", price: Money.eur(10), quantity: 1 },
  ]);

  await repo.save(order.confirm());
  const found = await repo.findById(order.id);

  expect(found).toEqual(order.confirm());
});

Kull adapter jikseb l-integration test tiegħu. Jekk l-adapter jgħaddi t-test tiegħu, u l-use case jgħaddi b’fake adapters, is-sistema kollha taħdem.

Meta Tuża l-Arkitettura Eżagonali

L-arkitettura eżagonali żżid struttura u indirezzjoni. Dik għandha spiża. Hawn meta jkun jistħoqqilha:

  • Applikazzjonijiet li jgħixu fit-tul. Jekk il-proġett se jinżamm għal snin, l-investiment jħallas malajr.
  • Regoli tan-negozju kumplessi. Jekk id-domain tiegħek għandu loġika mhux trivjali li teħtieġ tiġi ttestjata bir-reqqa.
  • Punti tad-dħul multipli. Jekk l-istess loġika tiġi msejħa minn web API, CLI, message queue, u cron job.
  • L-infrastruttura tista’ tinbidel. Jekk m’intix ċert jekk se tibqax mad-database attwali tiegħek, il-fornitur tal-cloud, jew servizzi ta’ parti terza.
  • Żviluppaturi multipli. Konfini ċari jnaqqsu l-merge conflicts u jagħmlu l-code reviews aktar mgħaġġla.

Meta Taqbiżha

Mhux kull proġett jeħtieġ dan il-livell ta’ struttura:

  • Applikazzjonijiet CRUD sempliċi. Jekk l-app qiegħda l-aktar taqra u tikteb data b’loġika minima tan-negozju, l-indirezzjoni żżid spiża mingħajr benefiċċju.
  • Prototipi u MVPs. Il-veloċita hija aktar importanti mill-arkitettura meta qiegħed tivvalida idea. Irrifatturja aktar tard.
  • Scripts u għodod żgħar. Għodda CLI ta’ 200 linja m’għandhiex bżonn ports u adapters.

L-ammont it-tajjeb ta’ arkitettura jiddependi fuq il-kumplessita tal-problema. Ibda sempliċi. Introduċi konfini eżagonali meta l-uġigħ li ma jkollokx isir reali.

Żbalji Komuni

Tnixxef l-infrastruttura fid-domain. Jekk l-entita Order tiegħek għandha decorator @Column jew metodu toJSON(), l-infrastruttura nixxfet ‘il ġewwa. Żomm l-oġġetti tad-domain tiegħek nodfa.

Toħloq ports li jirriflettu l-infrastruttura. Port imsejjaħ SqlQuery jegħleb l-iskop. Il-Ports għandhom jiddeskrivu dak li d-domain jeħtieġ bil-lingwa tad-domain, mhux kif l-infrastruttura taħdem.

Over-abstracting. Mhux kollox jeħtieġ port. Jekk għandek funzjoni ta’ utilita li tifformattja d-dati, sempliċement użaha direttament. Irriżerva l-ports għal konfini reali tal-infrastruttura.

Taqbiż il-composition root. Mingħajr post ċar fejn id-dipendenzi jiġu mqabbla, il-pattern tinħatt. Kull dipendenza għandha tiġi injected, mhux importata direttament.

Eżagonali vs. Patterns Oħra

Tista’ tistaqsi kif l-arkitettura eżagonali tirrelata ma’ patterns oħra magħrufa:

  • Clean Architecture (Robert C. Martin): Simili ħafna. Clean Architecture żżid aktar saffi (entities, use cases, interface adapters, frameworks) imma l-idea ewlenija hija l-istess: id-dipendenzi jippuntaw ‘il ġewwa.
  • Onion Architecture (Jeffrey Palermo): Wkoll simili ħafna. Onion Architecture tuża ċrieki konċentriċi bid-domain fiċ-ċentru. It-terminoloġija tvarja, imma r-regola tad-dipendenza hija identika.
  • Layered Architecture (N-tier): L-approċċ tradizzjonali fejn is-saffi jistakkjaw vertikalment (preżentazzjoni, negozju, data). Id-differenza kruċjali: fl-arkitettura bl-saffi, is-saff tan-negozju tipikament jiddependi fuq is-saff tad-data. Fl-arkitettura eżagonali, dik id-dipendenza hija inversa.

It-tliet patterns moderni kollha (eżagonali, clean, onion) jaqsmu l-istess prinċipju fundamentali. Id-domain ma jiddependix fuq l-infrastruttura. Huma differenti l-aktar f’konvenzjonijiet tal-ismijiet u l-għadd ta’ saffi li jippreskrivu.

Kif Tibda

Jekk trid tapplika l-arkitettura eżagonali għal proġett eżistenti, tippruvax tirrifatturja kollox f’daqqa. Ibda b’bounded context wieħed jew feature waħda:

  1. Identifika l-loġika ewlenija tan-negozju f’dik il-feature. Estraha f’funzjonijiet puri jew klassijiet bla dipendenzi fuq frameworks.
  2. Iddefinixxi ports għad-dipendenzi esterni li dik il-feature tuża. Oħloq interfaces bil-lingwa tad-domain.
  3. Mexxi l-kodiċi eżistenti tal-infrastruttura f’klassijiet adapter li jimplimentaw dawk l-interfaces.
  4. Għaqqad kollox flimkien f’composition root permezz ta’ dependency injection.
  5. Ikteb testijiet għad-domain u l-use cases permezz ta’ fake adapters.

Irrepeti għall-feature li jmiss. Maż-żmien, il-konfini eżagonali jinfirxu madwar il-codebase b’mod naturali.

Ħsibijiet Finali

L-arkitettura eżagonali mhijiex dwar li ssegwi template riġidu. Hija dwar idea sempliċi waħda: ipproteġi l-loġika tan-negozju tiegħek mill-kaos tad-dinja ta’ barra. Id-databases jinbidlu. Il-frameworks joħorġu mill-moda. L-APIs jiġu deprecate. Ir-regoli tan-negozju tiegħek għandhom jibqgħu ħajjin minn dak kollu.

Il-pattern taħdem għax tallinja ma’ kif is-software tabilħaqq tevolvi. Ir-rekwiżiti jinbidlu kostantement. L-infrastruttura tinbidel perioikament. Imma r-regoli ewlenin tan-negozju tiegħek għandhom tendenza li jkunu l-aktar parti stabbli tas-sistema. Ibni madwar dik l-istabilita.

Jekk qiegħed tibni prodott li jeħtieġ jibqa’, l-arkitettura eżagonali hija wieħed mill-aħjar investimenti li tista’ tagħmel fil-codebase tiegħek.


Teħtieġ għajnuna tiddisinja jew tirrifatturja l-arkitettura tal-applikazzjoni tiegħek? Ikkuntattjana. Ngħinu timijiet jibnu software li tibqa’ mantenibbli hekk kif tikber.

architecturehexagonalports and adaptersclean codesoftware design

Ejja nibnu l-proġett li jmiss tiegħek.

Ibbukkja telefonata b'xejn ta' 30 minuta. Niddiskutu l-għanijiet, il-kalendarju, u l-aħjar approċċ tiegħek. Ebda obbligu.

Ibbukkja konsultazzjoni hello@ryveris.com