{"id":15031659,"url":"https://github.com/eyhn/ddi","last_synced_at":"2025-04-10T00:21:32.666Z","repository":{"id":65966586,"uuid":"598436520","full_name":"EYHN/ddi","owner":"EYHN","description":"Dynamic dependency injection library for rust.","archived":false,"fork":false,"pushed_at":"2023-03-02T13:10:11.000Z","size":33,"stargazers_count":42,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-05T04:43:36.098Z","etag":null,"topics":["dependency-injection","rust","rustlang"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EYHN.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-07T05:15:51.000Z","updated_at":"2023-12-28T18:59:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"ded63f7b-62f3-4abc-b2e1-6e37e6cbbc79","html_url":"https://github.com/EYHN/ddi","commit_stats":{"total_commits":9,"total_committers":1,"mean_commits":9.0,"dds":0.0,"last_synced_commit":"2a8751210c94e41680fb96b80c0e8199a2027b33"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EYHN%2Fddi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EYHN%2Fddi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EYHN%2Fddi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EYHN%2Fddi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EYHN","download_url":"https://codeload.github.com/EYHN/ddi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248132122,"owners_count":21052977,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","rust","rustlang"],"created_at":"2024-09-24T20:16:16.442Z","updated_at":"2025-04-10T00:21:32.641Z","avatar_url":"https://github.com/EYHN.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DDI (dynamic dependency injection)\n\n[![](https://img.shields.io/crates/v/ddi)](https://crates.io/crates/ddi) ![](https://img.shields.io/crates/l/ddi)\n\nThis library provides a generic dependency injection container that can be easily integrated into any application and can be extremely extensible with the [extension trait](https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html).\n\nDependency injection is a common design pattern , mainly used in some frameworks such as [Rocket](https://rocket.rs/), [Actix Web](https://actix.rs/), [bevy](https://bevyengine.org/). With `ddi` you can implement dependency injection without such frameworks, and you can implement your own framework.\n\n\u003e scope feature (singleton vs transient vs scoped/request) is on the way!\n\n## Example\n\n```rust\nuse ddi::*;\n\nstruct TestService(String);\n\nlet mut services = ServiceCollection::new();\nservices.service(1usize);\nservices.service(\"helloworld\");\nservices.service_factory(|num: \u0026usize, str: \u0026\u0026str| Ok(TestService(format!(\"{}{}\", num, str))));\n\nlet provider = services.provider();\nassert_eq!(provider.get::\u003cTestService\u003e().unwrap().0, \"1helloworld\");\n```\n\n## `std` feature\n\n`ddi` supports `no-std` by default, if `std` feature enabled the internal data structure will be changed from [`alloc::collections::BTreeMap`] to [`std::collections::HashMap`] and [`std::error::Error`] will be implemented for [`DDIError`]. This will give a little performance improvement and usability.\n\n## `sync` feature\n\nIf `sync` feature enabled, `ddi` will support multi-threading and you can share [`ServiceProvider`] between multiple threads.\n\n\u003e! Enabling `sync` may cause your existing code to not compile! This is because enabling `sync` requires instances in the [`ServiceCollection`] to implement `send + sync` and `ServiceFactory` to implement `send`. And default no such restrictions.\n\n## Basic Usage\n\nFirst you need to register all services in the [`ServiceCollection`], which is a container of all services, [`ServiceCollection`] stored a series of triplets (type, name, implementation). You can use the [`ServiceCollection::service`] to add item to it.\n\nFor example, the following code will add a item (\u0026str, \"default\", \"helloworld\") to the [`ServiceCollection`]\n\n```rust ignore\nlet mut services = ServiceCollection::new();\nservices.service(\"helloworld\");\n```\n\nHere, the service \"implementation\" can also be a function, the factory of the service. The factory function is lazy execution, will only be executed when the service is used. For example.\n\n```rust ignore\nservices.service_factory(|| Ok(\"helloworld\"));\n```\n\nThe service factory can use parameters to get other services as dependencies. `ddi` will pass in the corresponding services based on the type of the parameters. Due to the reference rule of rust, the type of the parameter must be an immutable reference type.\n\n```rust ignore\nservices.service_factory(|dep: \u0026Foo| Ok(Bar::new(dep)));\n```\n\nWhen you have all the services registered, use [`ServiceCollection::provider()`] to get the [`ServiceProvider`], and then you can get any service you want from [`ServiceProvider`].\n\n```rust ignore\nlet provider = services.provider();\nassert_eq!(provider.get::\u003cTestService\u003e().unwrap().0, \"helloworld\");\n```\n\n## Design Patterns\n\n#### \\* Wrap your service with [`Service\u003cT\u003e`] (Arc)\n\nWhen a service wants to hold references to other services, the referenced service should be wrapped in [`Arc\u003cT\u003e`] for proper lifecycle handling. `ddi` defines an alias `type Service\u003cT\u003e = Arc\u003cT\u003e` for such a pattern.\n\nWe recommend wrapping all services in [`Service\u003cT\u003e`] to make cross-references between services easier.\n\nThat does not allow circular references, because `ddi` does not allow circular dependencies, which would cause the [`DDIError::CircularDependencyDetected`] error.\n\n```rust\nuse ddi::*;\n\nstruct Bar;\nstruct Foo(Service\u003cBar\u003e);\n\nlet mut services = ServiceCollection::new();\nservices.service(Service::new(Bar));\nservices.service_factory(\n  |bar: \u0026Service\u003cBar\u003e| Ok(Service::new(Foo(bar.clone())))\n);\n\nlet provider = services.provider();\nassert!(provider.get::\u003cService\u003cFoo\u003e\u003e().is_ok());\n```\n\n#### \\* Use extension trait to expanding [`ServiceCollection`]\n\nThe extension trait makes [`ServiceCollection`] extremely extensible. The following example shows the use of the extension trait to register multiple services into one function.\n\n```rust\nuse ddi::*;\n\n// ------------ definition ------------\n\n#[derive(Clone)]\nstruct DbConfiguration;\nstruct DbService(DbConfiguration, Service\u003cDbConnectionManager\u003e);\nstruct DbConnectionManager;\n\npub trait DbServiceCollectionExt: ServiceCollectionExt {\n    fn install_database(\u0026mut self) {\n      self.service(Service::new(DbConnectionManager));\n      self.service_factory(\n        |config: \u0026DbConfiguration, cm: \u0026Service\u003cDbConnectionManager\u003e|\n          Ok(Service::new(DbService(config.clone(), cm.clone())))\n      );\n      self.service(DbConfiguration);\n    }\n}\n\nimpl\u003cT: ServiceCollectionExt\u003e DbServiceCollectionExt for T {}\n\n// -------------- usage ---------------\n\nlet mut services = ServiceCollection::new();\n\nservices.install_database();\n\nlet provider = services.provider();\nassert!(provider.get::\u003cService\u003cDbService\u003e\u003e().is_ok());\n```\n\n#### \\* Use [`ServiceProvider`] in the factory, get other services dynamically\n\nIn our previous examples service factory used static parameters to get the dependencies, in the following example we use [`ServiceProvider`] to get the dependencies dynamically.\n\n```rust\nuse ddi::*;\n\ntrait Decoder: Send + Sync { fn name(\u0026self) -\u003e \u0026'static str; }\nstruct HardwareDecoder;\nstruct SoftwareDecoder;\nimpl Decoder for HardwareDecoder { fn name(\u0026self) -\u003e \u0026'static str { \"hardware\" } }\nimpl Decoder for SoftwareDecoder { fn name(\u0026self) -\u003e \u0026'static str { \"software\" } }\nstruct Playback {\n  decoder: Service\u003cdyn Decoder\u003e\n}\n\nconst SUPPORT_HARDWARE_DECODER: bool = false;\n\nlet mut services = ServiceCollection::new();\n\nif SUPPORT_HARDWARE_DECODER {\n  services.service(Service::new(HardwareDecoder));\n}\nservices.service(Service::new(SoftwareDecoder));\nservices.service_factory(\n  |provider: \u0026ServiceProvider| {\n    if let Ok(hardware) = provider.get::\u003cService\u003cHardwareDecoder\u003e\u003e() {\n      Ok(Playback { decoder: hardware.clone() })\n    } else {\n      Ok(Playback { decoder: provider.get::\u003cService\u003cSoftwareDecoder\u003e\u003e()?.clone() })\n    }\n  }\n);\n\nlet provider = services.provider();\nassert_eq!(provider.get::\u003cPlayback\u003e().unwrap().decoder.name(), \"software\");\n```\n\n#### \\* Use `service_var` or `service_factory_var` to register variants of service\n\nThe [`ServiceCollection`] can register multiple variants of the same type of service, using `service_var` or `service_factory_var`. When registering variants you need to declare ServiceName for each variant, the default (registered using the service or service_factory function) ServiceName is \"default\".\n\nThe following example demonstrates how to build an http server based on service variants.\n\n#[doc(cfg(not(feature = \"sync\")))]\n\n```rust\nuse ddi::*;\n\ntype Route = Service\u003cdyn Fn() -\u003e String\u003e;\nstruct HttpService {\n  routes: std::collections::HashMap\u003cString, Route\u003e\n}\nstruct BusinessService {\n  value: String\n}\n\nlet mut services = ServiceCollection::new();\n\nservices.service_var(\"/index\", Service::new(|| \"\u003chtml\u003e\".to_string()) as Route);\nservices.service_var(\"/404\", Service::new(|| \"404\".to_string()) as Route);\nservices.service_factory_var(\n  \"/business\",\n  |business: \u0026Service\u003cBusinessService\u003e| {\n    let owned_business = business.clone();\n    Ok(Service::new(move || owned_business.value.clone()) as Route)\n  }\n);\nservices.service_factory(\n  |provider: \u0026ServiceProvider| {\n    let routes = provider.get_all::\u003cRoute\u003e()?\n      .into_iter()\n      .map(|(path, route)| (path.to_string(), route.clone()))\n      .collect();\n    Ok(HttpService { routes })\n  }\n);\nservices.service(Service::new(BusinessService {\n  value: \"hello\".to_string()\n}));\n\nlet provider = services.provider();\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/index\").unwrap()(), \"\u003chtml\u003e\");\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/404\").unwrap()(), \"404\");\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/business\").unwrap()(), \"hello\");\n```\n\n#### \\* Use extension trait to simplify the registration of service variants\n\nIn the previous example we used `service_var` and `service_factory_var` to register routes for the http server, but the code were obscure and no type checking. The following example demonstrates use extension trait to simplify the definition of routes and solve these problems.\n\n```rust\nuse ddi::*;\n\n// ------------ definition ------------\n\ntype Route = Service\u003cdyn Fn() -\u003e String\u003e;\nstruct HttpService {\n  routes: std::collections::HashMap\u003cString, Route\u003e\n}\nstruct BusinessService {\n  value: String\n}\n\npub trait HttpCollectionExt: ServiceCollectionExt {\n    fn install_http(\u0026mut self) {\n      self.service_factory(\n        |provider: \u0026ServiceProvider| {\n          let routes = provider.get_all::\u003cRoute\u003e()?\n            .into_iter()\n            .map(|(path, route)| (path.to_string(), route.clone()))\n            .collect();\n          Ok(HttpService { routes })\n        }\n      );\n    }\n\n    fn install_route\u003cParam\u003e(\u0026mut self, path: \u0026'static str, route: impl ServiceFn\u003cParam, String\u003e + 'static) {\n      self.service_factory_var(path, move |provider: \u0026ServiceProvider| {\n        let owned_provider = provider.clone();\n        Ok(Service::new(move || route.run_with(\u0026owned_provider).expect(\"123\")) as Route)\n      })\n    }\n}\n\nimpl\u003cT: ServiceCollectionExt\u003e HttpCollectionExt for T {}\n\n// -------------- usage ---------------\n\nlet mut services = ServiceCollection::new();\n\nservices.install_route(\"/index\", || \"\u003chtml\u003e\".to_string());\nservices.install_route(\"/404\", || \"404\".to_string());\nservices.install_route(\"/business\", |business: \u0026BusinessService| business.value.to_string());\nservices.install_http();\n\nservices.service(BusinessService {\n  value: \"hello\".to_string()\n});\n\nlet provider = services.provider();\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/index\").unwrap()(), \"\u003chtml\u003e\");\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/404\").unwrap()(), \"404\");\nassert_eq!(provider.get::\u003cHttpService\u003e().unwrap().routes.get(\"/business\").unwrap()(), \"hello\");\n```\n\nThe `install_route` function in the example uses the [`ServiceFn`] trait as argument, which is a powerful type (of course we have [`ServiceFnMut`] and [`ServiceFnOnce`]), using the [`ServiceFn::run_with`] function to automatically extract Fn arguments from the [`ServiceProvider`] and execute it.\n\n# License\n\nThis project is licensed under [The MIT License](https://github.com/EYHN/ddi/blob/main/LICENSE).\n\n# Credits\n\nInspired by [Dependency injection in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyhn%2Fddi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feyhn%2Fddi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyhn%2Fddi/lists"}