Object Structural Pattern coined by Dr. Alistair Cockburn, in an article he wrote in 2005.
It promotes decoupling from technology and frameworks
Or
To a degree it's the Architectural extension of the Adapter pattern
...allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
Hexagonal Architecture
CORE
Outside
Outside
The Adapter Pattern
//target (our payment object interface)
interface IPayment {
id: string;
total: number;
submitPayment: Function;
}
//our payment class
class Payment implements IPayment {
constructor(public id: string, public total: number) {}
public submitPayment() {
console.log(`Proprietary Payment Amount: ${this.total} - ID: ${this.id}`);
}
}
//adaptee (3rd party lib)
interface PayPalPayment {
id: number; //abstract away the type in the adapter
amount: number; //abstract away the field name in the adapter
sendPayment: Function; //abstract away in the adapter
}
class ThirdPartyPayment implements PayPalPayment {
constructor(public id: number, public amount: number) {
this.id = id;
this.amount = amount;
}
public sendPayment() {
console.log(`3rd Party Payment Amount: ${this.amount} - ID: ${this.id}`);
}
}
enum PaymentType {
PayPal,
Proprietary
}
//adapter
class PaymentAdapter implements IPayment {
public id: string;
public total: number;
public type: PaymentType;
constructor(id: string, total: number, type: PaymentType) {
this.type = type;
this.id = id;
this.total = total;
}
public submitPayment() {
if (this.type === PaymentType.PayPal) {
const amount = this.total;
const id = parseInt(this.id);
const payment = new ThirdPartyPayment(id, amount);
payment.sendPayment();
} else if (this.type === PaymentType.Proprietary) {
const id = this.id.toString();
const payment = new Payment(id, this.total);
payment.submitPayment();
} else {
throw new Error('Invalid Payment Type');
}
}
}
//How we actuall use it
const payment: IPayment = new PaymentAdapter(
'123',
47.99,
PaymentType.Proprietary
);
payment.submitPayment();
const payment2: IPayment = new PaymentAdapter(
'543',
99.99,
PaymentType.PayPal
);
payment2.submitPayment();
Policy vs. Detail
When we're writing code, at any given time, we're either writing Policy or Detail
Policy
Abstractions, Interfaces, Business logic, declarative
When we're specifying what should happen, and when
Detail
Implementations, Infrastructure, Imperative
When we specify how the policy happens
Text
REST
CLI
MySQL
MongoDB
LogStach
Redis
File System
export interface IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult>;
}https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/
class EmailNotificationsService implements IHandle<AccountVerifiedEvent> {
private emailService: IEmailService;
constructor (emailService: IEmailService) {
DomainEvents.register(this.onAccountVerifiedEvent, AccountVerifiedEvent.name)
}
private onAccountVerifiedEvent (event: AccountVerifiedEvent): void {
emailService.sendMail({
to: event.user.email,
from: 'me@khalilstemmler.com',
message: "You're in, my dude"
})
}
}https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/
// We can define several valid implementations.
// This infra-layer code relies on the Domain layer
// email service.
class MailchimpEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}
class SendGridEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}
class MailgunEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}import { EmailNotificationsService } from './services/EmailNotificationsService'
import { MailchimpEmailService } from './infra/services/MailchimpEmailService'
import { SendGridEmailService } from './infra/services/SendGridEmailService'
import { MailgunEmailService } from './infra/services/MailgunEmailService'
const mailChimpService = new MailchimpEmailService();
const sendGridService = new SendGridEmailService();
const mailgunService = new MailgunEmailService();
// I can pass in any of these since they are all valid IEmailServices
new EmailNotificationsService(mailChimpService) https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/