Facade Pattern
Facade is a structural design pattern that provides a simplified (but limited) interface to a complex system of classes, library or framework.
While Facade decreases the overall complexity of the application, it also helps to move unwanted dependencies to one place.
Golang Implementation
It’s easy to underestimate the complexities that happen behind the scenes when you order a pizza using your credit card. There are dozens of subsystems that are acting in this process. Here’s just a shortlist of them:
- Check account
- Check security PIN
- Credit/debit balance
- Make ledger entry
- Send notification
In a complex system like this, it’s easy to get lost and easy to break stuff if you’re doing something wrong. That’s why there’s a concept of the Facade pattern: a thing that lets the client work with dozens of components using a simple interface. The client only needs to enter the card details, the security pin, the amount to pay, and the operation type. The Facade directs further communications with various components without exposing the client to internal complexities.
Example: Credit and Debit money from Wallet account
walletFacade.go
package main
import "fmt"
type WalletFacade struct {
account *Account
wallet *Wallet
securityCode *SecurityCode
notification *Notification
ledger *Ledger
}
func newWalletFacade(accountID string, code int) *WalletFacade {
fmt.Println("Starting create account")
walletFacacde := &WalletFacade{
account: newAccount(accountID),
securityCode: newSecurityCode(code),
wallet: newWallet(),
notification: &Notification{},
ledger: &Ledger{},
}
fmt.Println("Account created")
return walletFacacde
}
func (w *WalletFacade) addMoneyToWallet(accountID string, securityCode int, amount int) error {
fmt.Println("Starting add money to wallet")
err := w.account.checkAccount(accountID)
if err != nil {
return err
}
err = w.securityCode.checkCode(securityCode)
if err != nil {
return err
}
w.wallet.creditBalance(amount)
w.notification.sendWalletCreditNotification()
w.ledger.makeEntry(accountID, "credit", amount)
return nil
}
func (w *WalletFacade) deductMoneyFromWallet(accountID string, securityCode int, amount int) error {
fmt.Println("Starting debit money from wallet")
err := w.account.checkAccount(accountID)
if err != nil {
return err
}
err = w.securityCode.checkCode(securityCode)
if err != nil {
return err
}
err = w.wallet.debitBalance(amount)
if err != nil {
return err
}
w.notification.sendWalletDebitNotification()
w.ledger.makeEntry(accountID, "debit", amount)
return nil
}
account.go
package main
import "fmt"
type Account struct {
name string
}
func newAccount(accountName string) *Account {
return &Account{
name: accountName,
}
}
func (a *Account) checkAccount(accountName string) error {
if a.name != accountName {
return fmt.Errorf("Account Name is incorrect")
}
fmt.Println("Account Verified")
return nil
}
securityCode.go
package main
import "fmt"
type SecurityCode struct {
code int
}
func newSecurityCode(code int) *SecurityCode {
return &SecurityCode{
code: code,
}
}
func (s *SecurityCode) checkCode(incomingCode int) error {
if s.code != incomingCode {
return fmt.Errorf("Security Code is incorrect")
}
fmt.Println("SecurityCode Verified")
return nil
}
wallet.go
package main
import "fmt"
type Wallet struct {
balance int
}
func newWallet() *Wallet {
return &Wallet{
balance: 0,
}
}
func (w *Wallet) creditBalance(amount int) {
w.balance += amount
fmt.Println("Wallet balance added successfully")
return
}
func (w *Wallet) debitBalance(amount int) error {
if w.balance < amount {
return fmt.Errorf("Balance is not sufficient")
}
fmt.Println("Wallet balance is Sufficient")
w.balance = w.balance - amount
return nil
}
ledger.go
package main
import "fmt"
type Ledger struct {
}
func (s *Ledger) makeEntry(accountID, txnType string, amount int) {
fmt.Printf("Make ledger entry for accountId %s with txnType %s for amount %d\n", accountID, txnType, amount)
return
}
notification.go
package main
import "fmt"
type Notification struct {
}
func (n *Notification) sendWalletCreditNotification() {
fmt.Println("Sending wallet credit notification")
}
func (n *Notification) sendWalletDebitNotification() {
fmt.Println("Sending wallet debit notification")
}
main.go
package main
import (
"fmt"
"log"
)
func main() {
fmt.Println()
walletFacade := newWalletFacade("abc", 1234)
fmt.Println()
err := walletFacade.addMoneyToWallet("abc", 1234, 10)
if err != nil {
log.Fatalf("Error: %s\n", err.Error())
}
fmt.Println()
err = walletFacade.deductMoneyFromWallet("abc", 1234, 5)
if err != nil {
log.Fatalf("Error: %s\n", err.Error())
}
}
// Starting create account
// Account created
// Starting add money to wallet
// Account Verified
// SecurityCode Verified
// Wallet balance added successfully
// Sending wallet credit notification
// Make ledger entry for accountId abc with txnType credit for amount 10
// Starting debit money from wallet
// Account Verified
// SecurityCode Verified
// Wallet balance is Sufficient
// Sending wallet debit notification
// Make ledger entry for accountId abc with txnType debit for amount 5
Rust Implementation
Example:
pub struct WalletFacade hides a complex logic behind its API. A single method add_money_to_wallet interacts with the account, code, wallet, notification and ledger behind the scenes.
wallet_facade.rs
use crate::{
account::Account, ledger::Ledger, notification::Notification, security_code::SecurityCode,
wallet::Wallet,
};
/// Facade hides a complex logic behind the API.
pub struct WalletFacade {
account: Account,
wallet: Wallet,
code: SecurityCode,
notification: Notification,
ledger: Ledger,
}
impl WalletFacade {
pub fn new(account_id: String, code: u32) -> Self {
println!("Starting create account");
let this = Self {
account: Account::new(account_id),
wallet: Wallet::new(),
code: SecurityCode::new(code),
notification: Notification,
ledger: Ledger,
};
println!("Account created");
this
}
pub fn add_money_to_wallet(
&mut self,
account_id: &String,
security_code: u32,
amount: u32,
) -> Result<(), String> {
println!("Starting add money to wallet");
self.account.check(account_id)?;
self.code.check(security_code)?;
self.wallet.credit_balance(amount);
self.notification.send_wallet_credit_notification();
self.ledger.make_entry(account_id, "credit".into(), amount);
Ok(())
}
pub fn deduct_money_from_wallet(
&mut self,
account_id: &String,
security_code: u32,
amount: u32,
) -> Result<(), String> {
println!("Starting debit money from wallet");
self.account.check(account_id)?;
self.code.check(security_code)?;
self.wallet.debit_balance(amount);
self.notification.send_wallet_debit_notification();
self.ledger.make_entry(account_id, "debit".into(), amount);
Ok(())
}
}
wallet.rs
pub struct Wallet {
balance: u32,
}
impl Wallet {
pub fn new() -> Self {
Self { balance: 0 }
}
pub fn credit_balance(&mut self, amount: u32) {
self.balance += amount;
}
pub fn debit_balance(&mut self, amount: u32) {
self.balance
.checked_sub(amount)
.expect("Balance is not sufficient");
}
}
account.rs
pub struct Account {
name: String,
}
impl Account {
pub fn new(name: String) -> Self {
Self { name }
}
pub fn check(&self, name: &String) -> Result<(), String> {
if &self.name != name {
return Err("Account name is incorrect".into());
}
println!("Account verified");
Ok(())
}
}
ledger.rs
pub struct Ledger;
impl Ledger {
pub fn make_entry(&mut self, account_id: &String, txn_type: String, amount: u32) {
println!(
"Make ledger entry for accountId {} with transaction type {} for amount {}",
account_id, txn_type, amount
);
}
}
notification.rs
pub struct Notification;
impl Notification {
pub fn send_wallet_credit_notification(&self) {
println!("Sending wallet credit notification");
}
pub fn send_wallet_debit_notification(&self) {
println!("Sending wallet debit notification");
}
}
security_code.rs
pub struct SecurityCode {
code: u32,
}
impl SecurityCode {
pub fn new(code: u32) -> Self {
Self { code }
}
pub fn check(&self, code: u32) -> Result<(), String> {
if self.code != code {
return Err("Security code is incorrect".into());
}
println!("Security code verified");
Ok(())
}
}
main.rs
mod account;
mod ledger;
mod notification;
mod security_code;
mod wallet;
mod wallet_facade;
use wallet_facade::WalletFacade;
fn main() -> Result<(), String> {
let mut wallet = WalletFacade::new("abc".into(), 1234);
println!();
// Wallet Facade interacts with the account, code, wallet, notification and
// ledger behind the scenes.
wallet.add_money_to_wallet(&"abc".into(), 1234, 10)?;
println!();
wallet.deduct_money_from_wallet(&"abc".into(), 1234, 5)
}
// Starting create account
// Account created
//
// Starting add money to wallet
// Account verified
// Security code verified
// Sending wallet credit notification
// Make ledger entry for accountId abc with transaction type credit for amount 10
//
// Starting debit money from wallet
// Account verified
// Security code verified
// Sending wallet debit notification
// Make ledger entry for accountId abc with transaction type debit for amount 5