GithubHelp home page GithubHelp logo

init's People

Contributors

ferdinand-beyer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

init's Issues

Invoking `stop-fn` with `nil` when `start` fails

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.

Support adding constants to a config

  • Load config file
  • Build an init config
  • Add loaded config to the init config

Convenience; creates a component with start function (constantly x).

Configuration transformers

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:

  • Would it be convenient to discover transformers automatically, e.g. find components that are transformers?
  • Maybe provide a set of curated transformers?
    • conditionals
    • order-only dependencies
    • CDI-style alternatives
    • components that take the system map itself
    • etc.

This could be a convenient way for devs to pick the "features" they want, and be inspired to explore their own.

Support vars in `:init/inject`

  • Resolve a var to a component name
  • More direct: Users see directly what will be injected
  • Linters understand that a var is actually used
  • Survives refactoring
(defn ^:init/name foo []
  ::foo)

(defn use-foo
  {:init/inject [#'foo]}
  [foo]
  (println "Got: " foo))

Order-only dependencies

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

Partial reload

Consider this use case:

  • We're in development, running a REPL
  • A system is running
  • The config changes. Maybe one of the scanned namespaces got reloaded, or something else happened.
  • We restart just the parts that are affected by the change:
    • Stop removed components
    • Start new components
    • Restart components whose config has changed or whose dependencies need to restart.

Circular dependencies in test suites

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.

Add system API functions

  • Select instances similar to selecting components in a config
  • Resolve a selector, similar to how :init/inject would work (e.g. require one)

Use cases:

  • Get instances after starting the system, e.g. reporting to one of the components (an event bus maybe?) that the system is up.
  • Debugging on the REPL

Module system to tackle some disadvantages

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:

  • Consumer now just needs to inject module instead of individual functions.
  • Provider no longer need to maintain a large list of possibly duplicated injections, or init/name etc.
  • Functions within the same module don't need to use elaborate injections to call each other.

Bonus:

  • functions declaration order does not matter anymore.
  • no more "unused function" linter warning ๐Ÿ™‚ .

Is this something that you are interested in getting into init?

Merge nested configs

Combining discovery methods:

  • component :foo.init/config provides a config map
  • when added to a config, we can "promote" it to its parent config

Possible algorithm:

  • Select all components tagged with :init/config
  • Start these components
  • Replace the config keys with the component instances
  • Repeat until no more matching configs are found

Async support

  • Allow start functions to return a promise (promesa / core.async channel, use a protocol for that)
  • When at least one of the deps for a component to start is a promise, start the component once the promise resolves

Inject factory functions (scopes?)

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.