service_async/lib.rs
1//! # Service Async
2//! [](https://crates.io/crates/service-async)
3//!
4//! A `Service` trait similar to tower-service <https://docs.rs/tower/latest/tower/trait.Service.html>, in pure async style
5//!
6//! ## Motivation: Overcoming Limitations in Tower's Service Model
7//!
8//! The Tower framework's `Service` trait, while powerful, presents some challenges:
9//!
10//! 1. Limited Capture Scope: As a future factory used serially and spawned for parallel
11//! execution, Tower's `Service` futures cannot capture `&self` or `&mut self`. This
12//! necessitates cloning and moving ownership into the future.
13//!
14//! 2. Complex Poll-Style Implementation: Tower's `Service` trait is defined in a
15//! poll-style, requiring manual state management. This often leads to verbose
16//! implementations using `Box<Pin<...>>` to leverage async/await syntax.
17//!
18//! These limitations often result in code patterns like:
19//!
20//! ```rust
21//! impl<S, Req> tower::Service<Req> for SomeStruct<S>
22//! where
23//! // ...
24//! {
25//! type Response = // ...;
26//! type Error = // ...;
27//! type Future = Pin<Box<dyn Future<Output = ...> + Send + 'static>>;
28//!
29//! fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
30//! self.inner.poll_ready(cx)
31//! }
32//!
33//! fn call(&mut self, req: Req) -> Self::Future {
34//! let client = self.client.clone();
35//! Box::pin(async move {
36//! client.get(req).await;
37//! // ...
38//! })
39//! }
40//! }
41//! ```
42//! ## Introducing a Refined Service Trait
43//!
44//! This crate leverages `impl Trait` to introduce a new [`Service`](crate::Service)
45//! trait, designed to simplify implementation and improve performance:
46//!
47//! 1. Efficient Borrowing: By using `impl Trait` in the return position, futures
48//! can now capture `&self` or `&mut self`, eliminating unnecessary cloning.
49//!
50//! 2. Zero-Cost Abstractions: Utilizing `impl Trait` instead of `Box<dyn...>`
51//! allows for more inline code optimization, especially for operations not crossing await points.
52//!
53//! This approach combines the power of `impl Trait` with a refined [`Service`](crate::Service)
54//! trait to offer both flexibility and performance improvements.
55//!
56//! To enable parallel execution with this new design, we propose two approaches:
57//!
58//! 1. Shared Immutable Access: Use `&self` with a single `Service` instance.
59//! 2. Exclusive Mutable Access: Use `&mut self` and create a new `Service` instance for each call.
60//!
61//! The first approach is generally preferred, as mutable `Service`
62//! instances are often unnecessary for single-use scenarios.
63//!
64//! Our refined [`Service`](crate::Service) trait is defined as:
65//!
66//! ```rust
67//! pub trait Service<Request> {
68//! /// Responses given by the service.
69//! type Response;
70//! /// Errors produced by the service.
71//! type Error;
72//! /// Process the request and return the response asynchronously.
73//! fn call(&self, req: Request) -> impl Future<Output = Result<Self::Response, Self::Error>>;
74//! }
75//! ```
76//!
77//! This design eliminates the need for a `poll_ready` function, as state is maintained within the future itself.
78//!
79//! ## Key Differences and Advantages
80//!
81//! Compared to Tower's approach, our [`Service`](crate::Service) trait represents a paradigm shift:
82//!
83//! - Role: It functions as a request handler rather than a future factory.
84//! - State Management: Mutable state requires explicit synchronization
85//! primitives like `Mutex` or `RefCell`.
86//! - Resource Efficiency: Our approach maintains reference relationships,
87//! incurring costs only when mutability is required, unlike Tower's
88//! shared ownership model where each share has an associated cost.
89//!
90//! This refined [`Service`](crate::Service) trait offers a more intuitive, efficient,
91//! and flexible approach to building asynchronous services in Rust.
92//!
93//! ## MakeService
94//!
95//! The [`MakeService`](crate::MakeService) trait provides a flexible way to construct
96//! service chains while allowing state migration from previous instances. This is
97//! particularly useful when services manage stateful resources like connection pools,
98//! and you need to update the service chain with new configurations while preserving existing resources.
99//!
100//! Key features:
101//!
102//! - `make_via_ref` method allows creating a new service while optionally referencing an existing one.
103//! - Enables state preservation and resource reuse between service instances.
104//! - `make` method provides a convenient way to create a service without an existing reference.
105//!
106//! Example usage:
107//!
108//! ```rust
109//! struct SvcA {
110//! pass_flag: bool,
111//! not_pass_flag: bool,
112//! }
113//!
114//! struct InitFlag(bool);
115//!
116//! struct SvcAFactory {
117//! init_flag: InitFlag,
118//! }
119//!
120//! impl MakeService for SvcAFactory {
121//! type Service = SvcA;
122//! type Error = Infallible;
123//!
124//! fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
125//! Ok(match old {
126//! // SvcAFactory can access state from the older service
127//! // which was created.
128//! Some(r) => SvcA {
129//! pass_flag: r.pass_flag,
130//! not_pass_flag: self.init_flag.0,
131//! },
132//! // There was no older service, so create SvcA from
133//! // service factory config.
134//! None => SvcA {
135//! pass_flag: self.init_flag.0,
136//! not_pass_flag: self.init_flag.0,
137//! },
138//! })
139//! }
140//! }
141//! ```
142//!
143//! This approach allows for efficient updates to service chains, preserving valuable
144//! resources when reconfiguring services.
145//!
146//! # Service Factories and Composition
147//!
148//! ## Service Factories
149//!
150//! In complex systems, creating and managing services often requires more flexibility
151//! than a simple constructor can provide. This is where the concept of Service factories
152//! comes into play. A Service factory is responsible for creating instances of services,
153//! potentially with complex initialization logic or state management.
154//!
155//! ## MakeService Trait
156//!
157//! The [`MakeService`](crate::MakeService) trait is the cornerstone of our Service factory
158//! system. It provides a flexible way to construct service chains while allowing state
159//! migration from previous instances. This is particularly useful when services manage
160//! stateful resources like connection pools, and you need to update the service chain
161//! with new configurations while preserving existing resources.
162//!
163//! Key features of `MakeService`:
164//!
165//! - `make_via_ref` method allows creating a new service while optionally referencing an existing one.
166//! - Enables state preservation and resource reuse between service instances.
167//! - `make` method provides a convenient way to create a service without an existing reference.
168//!
169//! Example usage:
170//!
171//! ```rust
172//! struct SvcA {
173//! pass_flag: bool,
174//! not_pass_flag: bool,
175//! }
176//!
177//! struct InitFlag(bool);
178//!
179//! struct SvcAFactory {
180//! init_flag: InitFlag,
181//! }
182//!
183//! impl MakeService for SvcAFactory {
184//! type Service = SvcA;
185//! type Error = Infallible;
186//!
187//! fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
188//! Ok(match old {
189//! // SvcAFactory can access state from the older service
190//! // which was created.
191//! Some(r) => SvcA {
192//! pass_flag: r.pass_flag,
193//! not_pass_flag: self.init_flag.0,
194//! },
195//! // There was no older service, so create SvcA from
196//! // service factory config.
197//! None => SvcA {
198//! pass_flag: self.init_flag.0,
199//! not_pass_flag: self.init_flag.0,
200//! },
201//! })
202//! }
203//! }
204//! ```
205//!
206//! This approach allows for efficient updates to service chains,
207//! preserving valuable resources when reconfiguring services.
208//!
209//! ## FactoryLayer
210//!
211//! To enable more complex service compositions, we introduce the concept
212//! of [`FactoryLayer`](crate::layer::FactoryLayer). `FactoryLayer` is a
213//! trait that defines how to wrap one factory with another, creating a new
214//! composite factory. This allows for the creation of reusable, modular pieces
215//! of functionality that can be easily combined.
216//!
217//! To simplify chain assembly, factories can define a [`layer`](crate::layer::FactoryLayer::layer)
218//! function that creates a factory wrapper. This concept is similar to the
219//! Tower framework's `Layer`, but with a key difference:
220//!
221//! 1. Tower's `Layer`: Creates a `Service` wrapping an inner `Service`.
222//! 2. Our `layer`: Creates a `Factory` wrapping an inner `Factory`, which can then be used to create the entire `Service` chain.
223//!
224//! ## FactoryStack
225//!
226//! [`FactoryStack`](crate::stack::FactoryStack) is a powerful abstraction that allows
227//! for the creation of complex service chains. It manages a stack of service factories,
228//! providing methods to push new layers onto the stack and to create services from the assembled stack.
229//!
230//! The `FactoryStack` works by composing multiple `FactoryLayer`s together.
231//! Each layer in the stack wraps the layers below it, creating a nested structure
232//! of factories. When you call `make` or `make_async` on a `FactoryStack`, it
233//! traverses this structure from the outermost layer to the innermost, creating the complete service chain.
234//!
235//! This approach allows users to create complex service factories by chaining
236//! multiple factory layers together in an intuitive manner. Each layer can add
237//! its own functionality, modify the behavior of inner layers, or even completely transform the service chain.
238//!
239//! To create a chain of services using `FactoryStack`:
240//!
241//! 1. Start with a `FactoryStack` initialized with your configuration.
242//! 2. Use the `push` method to add layers to the stack.
243//! 3. Each layer can modify or enhance the behavior of the inner layers.
244//! 4. Finally, call `make` or `make_async` to create the complete service chain.
245//!
246//! This system offers a powerful and flexible way to construct and update
247//! service chains while managing state and resources efficiently. It allows for modular,
248//! reusable pieces of functionality, easy reconfiguration of service chains, and clear
249//! separation of concerns between different parts of your service logic.
250//!
251//! ## Putting it all together
252//!
253//! This example demonstrates the practical application of the [`MakeService`](crate::MakeService),
254//! [`FactoryLayer`](crate::layer::FactoryLayer), and [`FactoryStack`](crate::stack::FactoryStack)
255//! concepts. It defines several services (`SvcA` and `SvcB`) and their corresponding factories.
256//! The `FactoryStack` is then used to compose these services in a layered manner. The `Config`
257//! struct provides initial configuration, which is passed through the layers. Finally, in the
258//! `main` function, a service stack is created, combining `SvcAFactory` and `SvcBFactory`.
259//! The resulting service is then called multiple times, showcasing how the chain of services handles requests and maintains state.
260//!
261//! For a more comprehensive example that further illustrates these concepts and their advanced usage,
262//! readers are encouraged to examine the `demo.rs` file in the examples directory of the project.
263//!
264//! ```rust
265//! use std::{
266//! convert::Infallible,
267//! sync::atomic::{AtomicUsize, Ordering},
268//! };
269//!
270//! use service_async::{
271//! layer::{layer_fn, FactoryLayer},
272//! stack::FactoryStack,
273//! AsyncMakeService, BoxedMakeService, BoxedService, MakeService, Param, Service,
274//! };
275//!
276//! #[cfg(unix)]
277//! use monoio::main as main_macro;
278//! #[cfg(not(unix))]
279//! use tokio::main as main_macro;
280//!
281//! // ===== Svc*(impl Service) and Svc*Factory(impl NewService) =====
282//!
283//! struct SvcA {
284//! pass_flag: bool,
285//! not_pass_flag: bool,
286//! }
287//!
288//! // Implement Service trait for SvcA
289//! impl Service<()> for SvcA {
290//! type Response = ();
291//! type Error = Infallible;
292//!
293//! async fn call(&self, _req: ()) -> Result<Self::Response, Self::Error> {
294//! println!(
295//! "SvcA called! pass_flag = {}, not_pass_flag = {}",
296//! self.pass_flag, self.not_pass_flag
297//! );
298//! Ok(())
299//! }
300//! }
301//!
302//! struct SvcAFactory {
303//! init_flag: InitFlag,
304//! }
305//!
306//! struct InitFlag(bool);
307//!
308//! impl MakeService for SvcAFactory {
309//! type Service = SvcA;
310//! type Error = Infallible;
311//!
312//! fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
313//! Ok(match old {
314//! // SvcAFactory can access state from the older service
315//! // which was created.
316//! Some(r) => SvcA {
317//! pass_flag: r.pass_flag,
318//! not_pass_flag: self.init_flag.0,
319//! },
320//! // There was no older service, so create SvcA from
321//! // service factory config.
322//! None => SvcA {
323//! pass_flag: self.init_flag.0,
324//! not_pass_flag: self.init_flag.0,
325//! },
326//! })
327//! }
328//! }
329//!
330//! struct SvcB<T> {
331//! counter: AtomicUsize,
332//! inner: T,
333//! }
334//!
335//! impl<T> Service<usize> for SvcB<T>
336//! where
337//! T: Service<(), Error = Infallible>,
338//! {
339//! type Response = ();
340//! type Error = Infallible;
341//!
342//! async fn call(&self, req: usize) -> Result<Self::Response, Self::Error> {
343//! let old = self.counter.fetch_add(req, Ordering::AcqRel);
344//! let new = old + req;
345//! println!("SvcB called! {old}->{new}");
346//! self.inner.call(()).await?;
347//! Ok(())
348//! }
349//! }
350//!
351//! struct SvcBFactory<T>(T);
352//!
353//! impl<T> MakeService for SvcBFactory<T>
354//! where
355//! T: MakeService<Error = Infallible>,
356//! {
357//! type Service = SvcB<T::Service>;
358//! type Error = Infallible;
359//!
360//! fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
361//! Ok(match old {
362//! Some(r) => SvcB {
363//! counter: r.counter.load(Ordering::Acquire).into(),
364//! inner: self.0.make_via_ref(Some(&r.inner))?,
365//! },
366//! None => SvcB {
367//! counter: 0.into(),
368//! inner: self.0.make()?,
369//! },
370//! })
371//! }
372//! }
373//!
374//! // ===== impl layer fn for Factory instead of defining manually =====
375//!
376//! impl SvcAFactory {
377//! fn layer<C>() -> impl FactoryLayer<C, (), Factory = Self>
378//! where
379//! C: Param<InitFlag>,
380//! {
381//! layer_fn(|c: &C, ()| SvcAFactory {
382//! init_flag: c.param(),
383//! })
384//! }
385//! }
386//!
387//! impl<T> SvcBFactory<T> {
388//! fn layer<C>() -> impl FactoryLayer<C, T, Factory = Self> {
389//! layer_fn(|_: &C, inner| SvcBFactory(inner))
390//! }
391//! }
392//!
393//!
394//! // ===== Define Config and impl Param<T> for it =====
395//! #[derive(Clone, Copy)]
396//! struct Config {
397//! init_flag: bool,
398//! }
399//!
400//! impl Param<InitFlag> for Config {
401//! fn param(&self) -> InitFlag {
402//! InitFlag(self.init_flag)
403//! }
404//! }
405//!
406//! #[main_macro]
407//! async fn main() {
408//! let config = Config { init_flag: false };
409//! let stack = FactoryStack::new(config)
410//! .push(SvcAFactory::layer())
411//! .push(SvcBFactory::layer());
412//!
413//! let svc = stack.make_async().await.unwrap();
414//! svc.call(1).await.unwrap();
415//! svc.call(2).await.unwrap();
416//! svc.call(3).await.unwrap();
417//! }
418//! ```
419
420use std::future::Future;
421
422/// Provides the `Either` type for flexible service composition and conditional logic in layered architectures.
423pub mod either;
424/// Defines the `FactoryLayer` trait and utilities for creating composable factory wrappers in service architectures.
425pub mod layer;
426/// Provides the `FactoryStack` for composing and managing complex, layered service architectures.
427pub mod stack;
428/// Utilities to work with Serivices & factories
429pub mod utils;
430
431mod map;
432pub use map::MapTargetService;
433mod boxed;
434
435/// Trait for converting a service into a boxed service.
436pub use boxed::BoxService;
437
438// A factory for creating boxed services.
439pub use boxed::BoxServiceFactory;
440
441/// A type-erased wrapper for asynchronous service factories.
442pub use boxed::BoxedAsyncMakeService;
443
444/// A type-erased wrapper for services, enabling dynamic dispatch.
445pub use boxed::BoxedService;
446
447mod make_service;
448pub use make_service::{
449 ArcMakeBoxedService, ArcMakeService, AsyncMakeService, AsyncMakeServiceWrapper,
450 BoxedMakeBoxedService, BoxedMakeService, MakeService,
451};
452
453/// Item of type T has been set in a certain_map slot.
454pub use param::Param;
455
456/// Item of type T has been set in a certain_map slot and returns a reference.
457pub use param::ParamRef;
458
459/// Item of type T may have been set in a certain_map slot and returns Option<&T>
460pub use param::ParamMaybeRef;
461
462/// Item of type T has been set in a certain_map slot and returns a mutable reference.
463pub use param::ParamMut;
464
465/// Item of type T may have been set in a certain_map slot and returns Option<&mut T>.
466pub use param::ParamMaybeMut;
467
468/// Item of type T is vacant in certain_map slot.
469pub use param::ParamSet;
470
471/// Item of type T can be removed certain_map slot irrespective of it
472/// having been set before.
473pub use param::ParamRemove;
474
475/// Item of type T has been set in certain_map slot and can be removed
476/// from the slot, leaving it vacant.
477pub use param::ParamTake;
478
479/// This `Service` trait leverages `impl Trait` to offer a efficient and flexible
480/// approach to building asynchronous services in Rust. It addresses key challenges
481/// faced with Tower's `Service` trait:
482///
483/// 1. Efficient Borrowing: Futures can capture `&self` or `&mut self`, eliminating
484/// unnecessary cloning.
485/// 2. Simplified Implementation: Removes the need for manual state management and
486/// `Box<Pin<...>>`, allowing for more idiomatic async Rust code.
487///
488/// # Key Features
489///
490/// - Functions as a request handler rather than a future factory.
491/// - Eliminates the need for a `poll_ready` function.
492/// - Allows for more inline code optimization using `impl Trait`.
493/// - Supports both shared immutable (`&self`) and exclusive mutable (`&mut self`) access.
494///
495/// # Resource Efficiency
496///
497/// This design maintains reference relationships, incurring costs only when mutability
498/// is required, unlike Tower's shared ownership model where each share has an associated cost.
499pub trait Service<Request> {
500 /// The type of response returned by this service.
501 type Response;
502
503 /// The type of error that this service can produce.
504 type Error;
505
506 /// Asynchronously process the request and return the response.
507 ///
508 /// This method takes a shared reference to `self`, allowing for efficient
509 /// use of a single `Service` instance across multiple calls. For mutable state,
510 /// consider using synchronization primitives like `Mutex` or `RefCell`.
511 ///
512 /// # Arguments
513 ///
514 /// * `req` - The request to be processed by the service.
515 ///
516 /// # Returns
517 ///
518 /// A `Future` that resolves to a `Result` containing either the service's
519 /// response or an error.
520 fn call(&self, req: Request) -> impl Future<Output = Result<Self::Response, Self::Error>>;
521}