aboutsummaryrefslogtreecommitdiff
path: root/docs/facades.rst
blob: c067e3052a053a4b6bd7dad706663b7fa72da87f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
.. _docs-facades:

====================
Facades and backends
====================
This page explains what "facades" and "backends" mean in the context of Pigweed
and provides guidelines on when to use them.

------------------------------
What are facades and backends?
------------------------------
.. _top-down approach: https://en.wikipedia.org/wiki/Bottom%E2%80%93up_and_top%E2%80%93down_design

Let's take a `top-down approach`_, starting with high-level definitions.
In the context of a Pigweed module:

* A facade is an API contract of a module that must be satisfied at compile-time,
  i.e. a swappable dependency that changes the implementation of an API at
  compile-time.
* A backend is an implementation of a facade's contract.

Usually, the facade and the backend are in different modules. One module
exposes the facade contract, and another module is the backend that implements
the facade contract. The naming pattern that Pigweed follows is
``{facade_name}_{backend_name}``.

Facades, by design, don't need to be configured until they're actually used.
This makes it significantly easier to bring up features piece-by-piece.

Example: pw_log and pw_log_string
=================================
Here's a step-by-step walkthrough of how a real backend module implements
another module's facade.

.. _//pw_log/public/pw_log/log.h: https://cs.opensource.google/pigweed/pigweed/+/main:pw_log/public/pw_log/log.h
.. _//pw_log_string/public_overrides/pw_log_backend/log_backend.h: https://cs.opensource.google/pigweed/pigweed/+/main:pw_log_string/public_overrides/pw_log_backend/log_backend.h

* :ref:`module-pw_log` is a module that exposes a facade. The macros listed in
  :ref:`module-pw_log-macros` represent the API of the module.
* The ``#include "pw_log/log_backend.h"`` line in `//pw_log/public/pw_log/log.h`_
  represents the facade contract of ``pw_log``.
* :ref:`module-pw_log_string` is a backend module, It implements the
  ``pw_log/log_backend.h`` facade contract in
  `//pw_log_string/public_overrides/pw_log_backend/log_backend.h`_.
* In the build system there is a variable of some sort that specifies the
  backend. In Bazel there's a `label
  flag <https://bazel.build/extending/config#label-typed-build-settings>`_
  at ``//targets:pw_log_backend``. In CMake there's a ``pw_log_BACKEND``
  variable set to ``pw_log_string``. In GN there's a ``pw_log_BACKEND``
  variable set to ``dir_pw_log_string``.

.. note::

   There are a few more steps needed to get ``pw_log_string`` hooked up as the
   backend for ``pw_log`` but they aren't essential for the current discussion.
   See :ref:`module-pw_log_string-get-started-gn` for the details.

Example: Swappable OS libraries
===============================
The facade and backend system is similar to swappable OS libraries: you can
write code against ``libc`` (for example) and it will work so long as you have
an object to link against that has the symbols you're depending on. You can
swap in different versions of ``libc`` and your code doesn't need to know about
it.

A similar example from Windows is ``d3d9.dll``. Developers often swap this DLL
with a different library like ReShade to customize shading behavior.

Can a module have multiple facades?
===================================
Yes. The module-to-facade relationship is one-to-many. A module can expose
0, 1, or many facades. There can be (and usually are) multiple backend modules
implementing the same facade.

Is the module facade the same thing as its API?
===============================================
No. It's best to think of them as different concepts. The API is the interface
that downstream projects interact with. There's always a one-to-one relationship
between a module and its API. A facade represents the build system "glue" that
binds a backend to an API.

This is a common point of confusion because there are a few modules that
blur this distinction. Historically, the facade comprised the API as well as
the build system concept. We're moving away from this perspective.

Are Pigweed facades the same as the GoF facade design pattern?
==============================================================
.. _facade pattern: https://en.wikipedia.org/wiki/Facade_pattern

There are some loose similarities but it's best to think of them as different
things. The goal of the Gang of Four `facade pattern`_ is to compress some
ugly, complex API behind a much smaller API that is more aligned with your
narrow business needs. The motivation behind some Pigweed facades is loosely
the same: we don't want a device HAL to leak out into your include paths when
a facade is implemented.

------------------------------
Why does Pigweed have facades?
------------------------------
Pigweed's facades are basically a pattern that builds off the ideas of
`link-time subsitution <https://bramtertoolen.medium.com/91ffd4ef8687>`_.
Link-time subsitution only allows you to replace one source file with another,
whereas facades enable substituting program elements defined in *header files*.

Pigweed facades enable implementation flexibility with zero performance cost.
There are two ways to look at this. Continuing with the ``pw_log`` example:

* Organizations can reuse more code across projects by adopting the ``pw_log``
  API as their logging layer. Facades enable each project to customize how
  its logs are handled.
* For projects that want to integrate with ``pw_log`` but cannot adopt its
  API, facades can wrap the existing API which enables Pigweed to be compatible
  with the existing system.

Two of the major aspects of "implementation flexibility" enabled by facades are:

* Portability. For example, ``pw_sync`` needs platform-specific
  implementations to work correctly.
* Customized behavior. For example, you can make a fully portable ``pw_log``
  implementation, but what makes it special is the ability to tune it to your
  needs.

Why compile-time?
=================
Resolving facades and backends at compile-time enables:

* Call-site control from backends.
* Static allocation of backend-provided types.
* Explicit backend includes so it’s visually obvious you’re poking through
  abstraction.

--------------------------------
When to use facades and backends
--------------------------------
If you're trying to use a Pigweed module, and that module exposes a facade,
then you've got no choice: you've got to hook up a backend to fulfill that
facade contract or else the module won't work.

When to roll your own facades and backends
==========================================
* You need a global function or macro.
* You absolutely must avoid the overhead of virtual functions.

When to NOT roll your own facades and backends
==============================================
* If you can afford the runtime cost of dependency injection, use that.
  In all other cases where link-time subsitution will work, use that.
  Only if the API contract requires a backend to provide a header (which
  link-time substitution doesn't let you do) should you reach for a facde.
* You're trying to use globals to avoid dependency injection. Use
  the dependency injection! It makes testing much easier.
* Your needs can be served by a standard mechanism like virtual interfaces.
  Use the standard mechanism.