pezkuwi_sdk_docs/reference_docs/
frame_pallet_coupling.rs

1//! # FRAME Pezpallet Coupling
2//!
3//! This reference document explains how FRAME pallets can be combined to interact together.
4//!
5//! It is suggested to re-read [`crate::pezkuwi_sdk::frame_runtime`], notably the information
6//! around [`pezframe::pezpallet_macros::config`]. Recall that:
7//!
8//! > Configuration trait of a pezpallet: It allows a pezpallet to receive types at a later
9//! > point from the runtime that wishes to contain it. It allows the pezpallet to be parameterized
10//! > over both types and values.
11//!
12//! ## Context, Background
13//!
14//! FRAME pallets, as per described in [`crate::pezkuwi_sdk::frame_runtime`] are:
15//!
16//! > A pezpallet is a unit of encapsulated logic. It has a clearly defined responsibility and can
17//! > be
18//! linked to other pallets.
19//!
20//! That is to say:
21//!
22//! * *encapsulated*: Ideally, a FRAME pezpallet contains encapsulated logic which has clear
23//!   boundaries. It is generally a bad idea to build a single monolithic pezpallet that does
24//!   multiple things, such as handling currencies, identities and staking all at the same time.
25//! * *linked to other pallets*: But, adhering extensively to the above also hinders the ability to
26//!   write useful applications. Pallets often need to work with each other, communicate and use
27//!   each other's functionalities.
28//!
29//! The broad principle that allows pallets to be linked together is the same way through which a
30//! pezpallet uses its `Config` trait to receive types and values from the runtime that contains it.
31//!
32//! There are generally two ways to achieve this:
33//!
34//! 1. Tight coupling pallets.
35//! 2. Loose coupling pallets.
36//!
37//! To explain the difference between the two, consider two pallets, `A` and `B`. In both cases, `A`
38//! wants to use some functionality exposed by `B`.
39//!
40//! When tightly coupling pallets, `A` can only exist in a runtime if `B` is also present in the
41//! same runtime. That is, `A` is expressing that can only work if `B` is present.
42//!
43//! This translates to the following Rust code:
44//!
45//! ```
46//! trait Pallet_B_Config {}
47//! trait Pallet_A_Config: Pallet_B_Config {}
48//! ```
49//!
50//! Contrary, when pallets are loosely coupled, `A` expresses that some functionality, expressed via
51//! a trait `F`, needs to be fulfilled. This trait is then implemented by `B`, and the two pallets
52//! are linked together at the runtime level. This means that `A` only relies on the implementation
53//! of `F`, which may be `B`, or another implementation of `F`.
54//!
55//! This translates to the following Rust code:
56//!
57//! ```
58//! trait F {}
59//! trait Pallet_A_Config {
60//!    type F: F;
61//! }
62//! // Pallet_B will implement and fulfill `F`.
63//! ```
64//!
65//! ## Example
66//!
67//! Consider the following example, in which `pezpallet-foo` needs another pezpallet to provide the
68//! block author to it, and `pezpallet-author` which has access to this information.
69#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pezpallet_foo)]
70#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pezpallet_author)]
71//!
72//! ### Tight Coupling Pallets
73//!
74//! To tightly couple `pezpallet-foo` and `pezpallet-author`, we use Rust's supertrait system. When
75//! a pezpallet makes its own `trait Config` be bounded by another pezpallet's `trait Config`, it is
76//! expressing two things:
77//!
78//! 1. That it can only exist in a runtime if the other pezpallet is also present.
79//! 2. That it can use the other pezpallet's functionality.
80//!
81//! `pezpallet-foo`'s `Config` would then look like:
82#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)]
83//!
84//! And `pezpallet-foo` can use the method exposed by `pezpallet_author::Pezpallet` directly:
85#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)]
86//!
87//!
88//! ### Loosely  Coupling Pallets
89//!
90//! If `pezpallet-foo` wants to *not* rely on `pezpallet-author` directly, it can leverage its
91//! `Config`'s associated types. First, we need a trait to express the functionality that
92//! `pezpallet-foo` wants to obtain:
93#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)]
94//!
95//! > We sometimes refer to such traits that help two pallets interact as "glue traits".
96//!
97//! Next, `pezpallet-foo` states that it needs this trait to be provided to it, at the runtime
98//! level, via an associated type:
99#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)]
100//!
101//! Then, `pezpallet-foo` can use this trait to obtain the block author, without knowing where it
102//! comes from:
103#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)]
104//!
105//! Then, if `pezpallet-author` implements this glue-trait:
106#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pezpallet_author_provider)]
107//!
108//! And upon the creation of the runtime, the two pallets are linked together as such:
109#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)]
110//!
111//! Crucially, when using loose coupling, we gain the flexibility of providing different
112//! implementations of `AuthorProvider`, such that different users of a `pezpallet-foo` can use
113//! different ones, without any code change being needed. For example, in the code snippets of this
114//! module, you can find [`OtherAuthorProvider`], which is an alternative implementation of
115//! [`AuthorProvider`].
116#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)]
117//!
118//! A common pattern in pezkuwi-sdk is to provide an implementation of such glu traits for the unit
119//! type as a "default/test behavior".
120#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)]
121//!
122//! ## Frame System
123//!
124//! With the above information in context, we can conclude that **`pezframe_system` is a special
125//! pezpallet that is tightly coupled with every other pezpallet**. This is because it provides the
126//! fundamental system functionality that every pezpallet needs, such as some types like
127//! [`pezframe::prelude::pezframe_system::Config::AccountId`],
128//! [`pezframe::prelude::pezframe_system::Config::Hash`], and some functionality such as block number,
129//! etc.
130//!
131//! ## Recap
132//!
133//! To recap, consider the following rules of thumb:
134//!
135//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In
136//!   general, it is easier to argue about multiple pezpallet if they only communicate together via
137//!   a known trait, rather than having access to all of each others public items, such as storage
138//!   and dispatchables.
139//! * If a group of pallets is meant to work together, but is not foreseen to be generalized, or
140//!   used by others, consider tightly coupling pallets, *if it simplifies the development*.
141//! * If a pezpallet needs a functionality provided by another pezpallet, but multiple
142//!   implementations can be foreseen, consider loosely coupling pallets.
143//!
144//! For example, all pallets in `pezkuwi-sdk` that needed to work with currencies could have been
145//! tightly coupled with [`pezpallet_balances`]. But, `pezkuwi-sdk` also provides
146//! [`pezpallet_assets`] (and more implementations by the community), therefore all pallets use
147//! traits to loosely couple with balances or assets pezpallet. More on this in
148//! [`crate::reference_docs::frame_tokens`].
149//!
150//! ## Further References
151//!
152//! - <https://www.youtube.com/watch?v=0eNGZpNkJk4>
153//! - <https://exchange.pezkuwichain.app/questions/922/pezpallet-loose-couplingtight-coupling-and-missing-traits>
154//!
155//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider
156//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider
157
158#![allow(unused)]
159
160use pezframe::prelude::*;
161
162#[docify::export]
163#[pezframe::pezpallet]
164pub mod pezpallet_foo {
165	use super::*;
166
167	#[pezpallet::config]
168	pub trait Config: pezframe_system::Config {}
169
170	#[pezpallet::pezpallet]
171	pub struct Pezpallet<T>(_);
172
173	impl<T: Config> Pezpallet<T> {
174		fn do_stuff_with_author() {
175			// needs block author here
176		}
177	}
178}
179
180#[docify::export]
181#[pezframe::pezpallet]
182pub mod pezpallet_author {
183	use super::*;
184
185	#[pezpallet::config]
186	pub trait Config: pezframe_system::Config {}
187
188	#[pezpallet::pezpallet]
189	pub struct Pezpallet<T>(_);
190
191	impl<T: Config> Pezpallet<T> {
192		pub fn author() -> T::AccountId {
193			todo!("somehow has access to the block author and can return it here")
194		}
195	}
196}
197
198#[pezframe::pezpallet]
199pub mod pezpallet_foo_tight {
200	use super::*;
201
202	#[pezpallet::pezpallet]
203	pub struct Pezpallet<T>(_);
204
205	#[docify::export(tight_config)]
206	/// This pezpallet can only live in a runtime that has both `pezframe_system` and
207	/// `pezpallet_author`.
208	#[pezpallet::config]
209	pub trait Config: pezframe_system::Config + pezpallet_author::Config {}
210
211	#[docify::export(tight_usage)]
212	impl<T: Config> Pezpallet<T> {
213		// anywhere in `pezpallet-foo`, we can call into `pezpallet-author` directly, namely because
214		// `T: pezpallet_author::Config`
215		fn do_stuff_with_author() {
216			let _ = pezpallet_author::Pezpallet::<T>::author();
217		}
218	}
219}
220
221#[docify::export]
222/// Abstraction over "something that can provide the block author".
223pub trait AuthorProvider<AccountId> {
224	fn author() -> AccountId;
225}
226
227#[pezframe::pezpallet]
228pub mod pezpallet_foo_loose {
229	use super::*;
230
231	#[pezpallet::pezpallet]
232	pub struct Pezpallet<T>(_);
233
234	#[docify::export(loose_config)]
235	#[pezpallet::config]
236	pub trait Config: pezframe_system::Config {
237		/// This pezpallet relies on the existence of something that implements [`AuthorProvider`],
238		/// which may or may not be `pezpallet-author`.
239		type AuthorProvider: AuthorProvider<Self::AccountId>;
240	}
241
242	#[docify::export(loose_usage)]
243	impl<T: Config> Pezpallet<T> {
244		fn do_stuff_with_author() {
245			let _ = T::AuthorProvider::author();
246		}
247	}
248}
249
250#[docify::export(pezpallet_author_provider)]
251impl<T: pezpallet_author::Config> AuthorProvider<T::AccountId> for pezpallet_author::Pezpallet<T> {
252	fn author() -> T::AccountId {
253		pezpallet_author::Pezpallet::<T>::author()
254	}
255}
256
257pub struct OtherAuthorProvider;
258
259#[docify::export(other_author_provider)]
260impl<AccountId> AuthorProvider<AccountId> for OtherAuthorProvider {
261	fn author() -> AccountId {
262		todo!("somehow get the block author here")
263	}
264}
265
266#[docify::export(unit_author_provider)]
267impl<AccountId> AuthorProvider<AccountId> for () {
268	fn author() -> AccountId {
269		todo!("somehow get the block author here")
270	}
271}
272
273pub mod runtime {
274	use super::*;
275	use pezcumulus_pezpallet_aura_ext::pezpallet;
276	use pezframe::{runtime::prelude::*, testing_prelude::*};
277
278	construct_runtime!(
279		pub struct Runtime {
280			System: pezframe_system,
281			PalletFoo: pezpallet_foo_loose,
282			PalletAuthor: pezpallet_author,
283		}
284	);
285
286	#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
287	impl pezframe_system::Config for Runtime {
288		type Block = MockBlock<Self>;
289	}
290
291	impl pezpallet_author::Config for Runtime {}
292
293	#[docify::export(runtime_author_provider)]
294	impl pezpallet_foo_loose::Config for Runtime {
295		type AuthorProvider = pezpallet_author::Pezpallet<Runtime>;
296		// which is also equivalent to
297		// type AuthorProvider = PalletAuthor;
298	}
299}