ferdinand-beyer / init Goto Github PK
View Code? Open in Web Editor NEWDependency injection a la carte
License: MIT License
Dependency injection a la carte
License: MIT License
When a start function throws, init.system/start-component
will attempt to stop all already started components, in reverse dependency order.
However, this calculation seems to be wrong, as I see suppressed NullPointerExceptions
.
We should limit the reverse-dependency-order
to the set of keys in the system
at the time of error.
Convenience; creates a component with start function (constantly x)
.
Many features that more complex DI frameworks provide can be done in init by simply transforming the configuration.
For example, consider Spring's @Conditional
annotation, which will include beans only when certain conditions are met. Common examples are the presence of a certain class on the classpath (e.g. to auto-configure adapter beans) or beans of certain types (e.g. to provide defaults).
In init, we can simply discover the configuration, then remove components whose prerequisites are not met.
How do we configure such conditions? We can use tags, and/or add custom keys to components (components are just maps). When discovering components from vars, we can access the var's metadata.
How do we configure transformers? Since discovery + starting are two separate steps, we can transform the configuration before starting the system.
Explore:
This could be a convenient way for devs to pick the "features" they want, and be inspired to explore their own.
(defn ^:init/name foo []
::foo)
(defn use-foo
{:init/inject [#'foo]}
[foo]
(println "Got: " foo))
Allows modelling that a component needs to be started before another, without injecting the instance to the start function.
Use Case: Logging, and other libs that use global state
Instead of injecting a "logger" component, allow components to log with a global state, but ensure that logging is configured beforehand
Consider this use case:
In Clojure, tests are conventionally located in prefix.ns-test
, it post a small challenge in using init
. Because for example, if I use init
to scan namespace prefix company
, it would normally auto require all test case namespaces to form system graph
, however, those integration test cases would normally need to import the system graph
, which results in circular dependencies.
flowchart LR
root[company.main] -->|Scan| test(company.main-test)
test -->|need system graph| root
If I want to keep the clojure test namespace naming convention, there seems to be no good workaround for now. My current workaround leverage runtime (resolve)
+ kaocha
hooks, but it's a bit ugly.
I wonder if it's worth to have a granular control when init
do scan, perhaps, support filtering during scan? For example, we can allow pass in a filter function to filter out certain namespaces, such as xxxx-test
.
:init/inject
would work (e.g. require one)Use cases:
Use cases:
Hi, thank you so much for making init. It's closest to what I want as a DI/IoC system in Clojure. Since the release I have used this in production extensively.
Overtime, I found a problem with init
is that it's a little bit too granular. Meaning that we ended up with a lot of small functions being controlled by init
. The producer side has a lot of duplicated injections. The consumer side often needs to inject a dozens of small functions too.
We since implemented a concept called module
to tackle this, it's similar to OOP or CLOS but with init
integrated. This is how it looks like in source code.
(defmodule ::test-module
[:company.config/config]
; Methods can be defined inline like these
(:method1 []
; methods get access to module dependencies implicitly.
(keys config))
(:read [x]
; "this" is a reserved keyword used to call other methods of the same module
(this :bar x))
(:bar [x] {:bar x}))
; Method can also be defined separately
(defm ::test-module :foo [kw]
; The config is bounded to the module dependency implicitly.
(kw config))
;; To use
(defn bluhbluh
{:init/inject [::test-module]}
[test-mod]
(test-mod :foo keys)) ; => [:a :b ...]
We found the benefits include:
init/name
etc.Bonus:
Is this something that you are interested in getting into init
?
Combining discovery methods:
:foo.init/config
provides a config mapPossible algorithm:
:init/config
A component may chose to dynamically obtain instances of a dependency.
Use case: Create a new Reitit router for every call in development.
Currently, this is possible by making the component instance a function, but this seems to complect two different things: Constructing an instance and deciding how to inject it.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.