Design patterns - let's build the Facade

2022-09-158 min
Design patterns - let's build the Facade

Introduction

Hello, In this article I would like to talk about Facade Pattern, what is it and how and when to use it. Before We dig into details I want to first put some light on Design patterns in general.

Design Patterns

During programming We often run into commonly occurring problems within the context of software design. Sometimes We try to solve it without having any organised plan. It happens that it will work, but on the other hand, it may lead to unreadable and hard-maintainable code. Probably many of us had a moments of doubt when QA reports another bug for the same feature. Those are the symptoms of bad software design. Fortunately design patterns come with help.

Design pattern is a description or template for how to solve a software design problem that can be used in many different situations. Patterns are divided into 4 classifications:

  • Creational patterns,
  • Structural patterns,
  • Behavioral patterns,
  • Concurrency patterns,

Facade pattern - topic of this article - is classified as Structural pattern.

Facade Pattern

Facade is a unified and simplfied front-face interface, which encapsulates one or many subsystems interfaces. Below diagram illustrates the structure of this pattern:

Facade diagram

Let's imagine turning on the laptop. The only thing you do is pressing the power button, but the laptop itself goes through a 'boot up' process: it supplies the power to its components, loads the UEFI or BIOS, initializes hardware and loads the full OS. Luckily, as a user, you don't have to know that to successfully turn on your laptop. Power button is a Facade for you. This is exactly how Facade pattern works in context of sofware engineering. It hides the complexity and provides simplified interface according to the needs.

What are the characteristics of Facade:

  • simplifies the communication between client and subsystems,
  • expose only what's needed,
  • encapsulates one or many subsystems interfaces,
  • hide the complexity,
  • don't have much logic,
  • is not a mediator for the subsystems(as in mediator pattern),
  • it's not a 'god' object (knows and do everything),
  • better loose coupling since client knows only about facade,

Ok, time to put a theory into practice!

Implementing Facade

Let's consider following scenario: We want to implement a payment feature. It requires a lot of data and business logic: informations about user account, account balance, account type, list of payees, payee data, payment currency, payment costs and many, many more. I don't want to fulfill all business requirements here, instead just give an example and idea of Facade pattern usage. I will be using Angular framework, but it doesn't matter, the idea matters.

Below the examples of the services:

Services

account.service.ts

@Injectable({
  providedIn: 'root'
})
export class AccountService {
  public account = this.getAccount();

  constructor() { }

  private getAccount(): Account {
    return {
      name: 'John',
      balance: 1000,
      currency: Currency.USD,
      /// other account data
    };
  }
}

account-balance.service.ts

@Injectable({
  providedIn: 'root'
})
export class AccountBalanceService {

  constructor(private accountService: AccountService) { }

  private get account(): Account {
    return this.accountService.account;
  }

  public get balance(): number {
    return this.account.balance;
  }

  public isOverdraft(amount: number): boolean {
    return amount > this.balance;
  }
}

currency.service.ts

@Injectable({ providedIn: 'root' })
export class CurrencyService {
  accountCurrency = this.accountService.account.currency;
  currencies$ = new BehaviorSubject<Currency[]>([this.accountCurrency]);

  constructor(private accountService: AccountService) {}

  setCurrencies(currency: Currency): void {
    const currencies = [this.accountCurrency, currency].filter(onlyUnique);
    this.currencies$.next(currencies);
  }
}

payees.service.ts

@Injectable({
  providedIn: 'root'
})
export class PayeesService {
  public payees$ = this.getPayees();

  constructor(private http: HttpClient) {}

  private getPayees(): Observable<Account[]> {
    return this.http.get<Account[]>(`${ environment.apiUrl }/payees`);
  }
}

payment-form.factory.ts

@Injectable({ providedIn: 'root' })
export class PaymentFormFactory {
  constructor(private accountBalanceService: AccountBalanceService) {}

  buildForm(initialValue: Partial<Payment> = {}): FormGroup {
    const form = new FormGroup({
      payee: new FormControl(null, Validators.required),
      amount: new FormControl(null, [Validators.required, Validators.max(this.accountBalanceService.balance)]),
      currency: new FormControl(null, Validators.required),
    });

    form.patchValue(initialValue);

    return form;
  }
}

payment.service.ts

@Injectable({ providedIn: 'root' })
export class PaymentService {
  paymentForm = this.paymentFormFactory.buildForm({
    currency: this.currencyService.accountCurrency
  });

  get payeeControl(): FormControl {
    return this.paymentForm.get('payee') as FormControl;
  }

  get amountControl(): FormControl {
    return this.paymentForm.get('amount') as FormControl;
  }

  get currencyControl(): FormControl {
    return this.paymentForm.get('currency') as FormControl;
  }

  constructor(
    private paymentFormFactory: PaymentFormFactory,
    private currencyService: CurrencyService,
  ) {
    this.payeeControl
      .valueChanges
      .subscribe((payee: Account) => this.currencyService.setCurrencies(payee.currency))
  }

  makePayment(): void {
    // call to API
  }
}

Facade

And here is the Facade:

payment.facade.ts

@Injectable({ providedIn: 'root' })
export class PaymentFacade {
  account = this.accountService.account;
  balance = this.accountBalanceService.balance;
  isOverdraft = this.accountBalanceService.isOverdraft;
  
  paymentForm = this.paymentService.paymentForm;

  payees$ = this.payeesService.payees$;
  currencies$ = this.currencyService.currencies$.asObservable();

  constructor(
    private paymentService: PaymentService,
    private payeesService: PayeesService,
    private currencyService: CurrencyService,
    private accountService: AccountService,
    private accountBalanceService: AccountBalanceService,
  ) {}

  makePayment(): void {
    // some other rules, checks, payment types, dates etc..
    this.paymentService.makePayment();
  }
}

Component

payment.component.ts

@Component({
  selector: 'app-payment',
  templateUrl: './payment.component.html',
  styleUrls: ['./payment.component.scss']
})
export class PaymentComponent {
  currencies$ = this.paymentFacade.currencies$;
  payees$ = this.paymentFacade.payees$;
  paymentForm = this.paymentFacade.paymentForm;
  balance = this.paymentFacade.balance;

  constructor(private paymentFacade: PaymentFacade) { }

  onSubmit(): void {
    this.paymentFacade.makePayment();
  }
}

I know, there is a lot of code, but I hope u notice the following:

  • we have a lot of services(in real payment implementation we would have much more services and logic),
  • services are communicating with each other,
  • facade injects all services, and expose properties, methods needed by component,
  • component doesn't know about any of the service,
  • component communicates only with facade,
  • facade doesn't have any logic,

Facade downsides

There is no perfect solution without any downsides, so let's list them below:

  • more complex implementation in the context of additional abstraction layer,
  • additional level of indirection,

Conclusion

There is no definite answer if you should use Facades in your project. Before doing so, make certain that this pattern solves your software design problem. For sure doing something without strong reasons, especially in regards to choosing patterns or solving architectural problems is really bad idea. At the end, knowledge of design patterns is really useful, it makes us much more concious which abounds in better code quality.