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.
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:
- L-ittestjar isir ta’ uġigħ. Ma tistax tittestja r-regoli tan-negozju mingħajr ma tqajjem database jew timmokka nofs il-framework.
- 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.
- 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:
- Identifika l-loġika ewlenija tan-negozju f’dik il-feature. Estraha f’funzjonijiet puri jew klassijiet bla dipendenzi fuq frameworks.
- Iddefinixxi ports għad-dipendenzi esterni li dik il-feature tuża. Oħloq interfaces bil-lingwa tad-domain.
- Mexxi l-kodiċi eżistenti tal-infrastruttura f’klassijiet adapter li jimplimentaw dawk l-interfaces.
- Għaqqad kollox flimkien f’composition root permezz ta’ dependency injection.
- 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.