BackendBit

К списку паттернов

Facade предоставляет упрощённый интерфейс для доступа к системе.

Фасад используется, когда в системе есть набор компонентов, которые взаимодействуют друг с другом. Клиентскому коду не всегда требуется использовать все эти компоненты – иногда ему нужна лишь высокоуровневая операция.

UML диаграмма классов паттерна ФасадПоказывает класс OrderFacade с методами place и cancel, который связан с тремя подсистемами: InventoryService, PaymentService и DeliveryService.OrderFacade+place(OrderDetails): void+cancel(OrderDetails): voidInventoryServicePaymentServiceDeliveryServiceФасадПодсистемы

Пример

Представим, что у нас есть интернет-магазин. Основная фича, которая нужна покупателю – сделать заказ.

Но при этом бизнес-процессы интернет-магазина устроены сложно. Он состоит из многих подсистем, таких как: склад, доставка, колл-центр и так далее.

Клиенту совершенно не нужно знать, как это всё работает «под капотом». Как мы уже сказали – всё, что ему нужно это сделать заказ.

Поэтому мы можем представить функционал работы с заказом в виде фасада:

package ecommerce;

public final class OrderFacade {
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final DeliveryService deliveryService;

    public OrderFacade(InventoryService inventoryService,
                       PaymentService paymentService,
                       DeliveryService deliveryService) {
        this.inventoryService = inventoryService;
        this.paymentService = paymentService;
        this.deliveryService = deliveryService;
    }

    /**
     * Создать заказ.
     */
    public void place(OrderDetails orderDetails) {
        // Резервируем товары из заказа на складе
        inventoryService.reserveItems(orderDetails.items());

        // Оплачиваем заказ
        paymentService.pay(orderDetails.paymentDetails());

        // Передаём заказ в доставку
        deliveryService.transferForDelivery(orderDetails.deliveryDetails());
    }

    /**
     * Отменить заказ.
     */
    public void cancel(OrderDetails orderDetails) {
        // Отменяем резервацию товаров на складе
        inventoryService.cancelReservation(orderDetails.items());

        // Возвращаем деньги клиенту
        paymentService.refund(orderDetails.paymentDetails());

        // Отменяем доставку
        deliveryService.cancelDelivery(orderDetails.deliveryDetails());
    }
}
java
package ecommerce;

import java.util.List;

public record OrderDetails(
    List<Item> items,
    PaymentDetails paymentDetails,
    DeliveryDetails deliveryDetails
) {}
java

Для того чтобы создать заказ, нужно:

  1. Зарезервировать на складе товары из заказа.
  2. Получить оплату за заказ.
  3. Передать заказ в доставку.

Для того чтобы отменить заказ, нужно:

  1. Отменить резервацию товаров на складе.
  2. Вернуть покупателю деньги.
  3. Отменить доставку заказа.

Как видно, в операции создания и отмены заказа участвуют 3 компонента (или подсистемы):

  • Inventory – сервис склада, для резервирования товаров.
  • Payment – сервис оплаты заказа.
  • Delivery – сервис доставки заказа.

Класс OrderFacade выполняет функцию фасада, предоставляя пользователю удобный интерфейс для работы с заказом. Пользователю не нужно знать о компонентах InventoryService, PaymentService и DeliveryService. Для того чтобы создать заказ, достаточно такого вызова:

orderFacade.place(orderDetails);
java

А для отмены заказа:

orderFacade.cancel(orderDetails);
java

Реальные примеры

EntityManager из Hibernate

Hibernate – это Data Mapper ORM для Java. Он маппит объекты на таблицы базы данных и реализует спецификацию JPA (Jakarta Persistence API).

JPA-интерфейс EntityManager предоставляет фасад для работы с базой данных через ORM. Методы persist() и find() скрывают сложную цепочку действий: получение JDBC-соединения из пула, генерацию SQL из маппингов сущностей, управление кэшем первого уровня и координацию транзакций.

Клиентскому коду не нужно знать обо всех этих подсистемах – достаточно простых вызовов:

// EntityManager – фасад: скрывает JDBC, генерацию SQL,
// кэширование и управление транзакциями
Order order = new Order(items, paymentDetails, deliveryDetails);
em.persist(order);

Order found = em.find(Order.class, 42L);
java

За вызовом persist() стоит целая подсистема: ConnectionProvider выдаёт JDBC-соединение из пула, ActionQueue определяет порядок выполнения операций при flush, PersistenceContext управляет кэшем первого уровня и отслеживает изменения (dirty checking), а TransactionCoordinator координирует фиксацию транзакции. Метод persist() скрывает всё это за одним вызовом.

database/sql

Пакет database/sql из стандартной библиотеки Go предоставляет фасад для работы с базами данных. Тип sql.DB скрывает управление пулом соединений, взаимодействие с драйвером базы данных и подготовку запросов.

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq" // Драйвер PostgreSQL
)

func main() {
	// sql.Open – фасад: скрывает выбор драйвера, создание пула соединений
	// и управление их жизненным циклом
	db, err := sql.Open("postgres", "host=localhost dbname=shop sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// db.QueryRow – фасад: скрывает получение соединения из пула,
	// подготовку запроса и возврат соединения в пул
	var name string
	err = db.QueryRow("SELECT name FROM products WHERE id = $1", 42).Scan(&name)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(name)
}
go

За вызовами QueryRow(), Exec() и Begin() стоят подсистемы: пул соединений, менеджер драйверов (driver.Driver) и подготовка запросов (driver.Stmt). Тип sql.DB скрывает всю эту сложность и предоставляет клиентскому коду простой интерфейс.