项目作者: Cado-Labs

项目描述 :
Thread-safe semanticaly-defined IoC/DI Container (https://github.com/smart-rb)
高级语言: Ruby
项目地址: git://github.com/Cado-Labs/smart_container.git
创建时间: 2021-07-21T09:04:25Z
项目社区:https://github.com/Cado-Labs/smart_container

开源协议:MIT License

下载


" class="reference-link">SmartCore::Container · Supported by Cado Labs · Gem Version

Thread-safe semanticaly-defined IoC/DI Container.




Supported by Cado Labs


Installation

  1. gem 'smart_container'
  1. bundle install
  2. # --- or ---
  3. gem install smart_container
  1. require 'smart_core/container'

Table of cotnents


Functionality

container class creation

  1. class Container < SmartCore::Container
  2. namespace(:database) do # support for namespaces
  3. register(:resolver, memoize: true) { SomeDatabaseResolver.new } # dependency registration
  4. namespace(:cache) do # support for nested naespaces
  5. register(:memcached, memoize: true) { MemcachedClient.new }
  6. register(:redis, memoize: true) { RedisClient.new }
  7. end
  8. end
  9. # root dependencies
  10. register(:logger, memoize: true) { Logger.new(STDOUT) }
  11. # dependencies are not memoized by default (memoize: false)
  12. register(:random) { rand(1000) }
  13. end

mixin

  1. # full documentaiton is coming;
  2. class Application
  3. include SmartCore::Container::Mixin
  4. dependencies do
  5. namespace(:database) do
  6. register(:cache) { MemcachedClient.new }
  7. end
  8. end
  9. end
  10. # access:
  11. Application.container
  12. Application.new.container # NOTE: the same instance as Application.container

container instantiation and dependency resolving

  1. container = Container.new # create container instance
  1. container['database.resolver'] # => #<SomeDatabaseResolver:0x00007f0f0f1d6332>
  2. container['database.cache.redis'] # => #<RedisClient:0x00007f0f0f1d0158>
  3. container['logger'] # => #<Logger:0x00007f5f0f2f0158>
  4. container.resolve('logger') # #resolve(path) is an alias for #[](path)
  5. # non-memoized dependency
  6. container['random'] # => 352
  7. container['random'] # => 57
  8. # trying to resolve a namespace as dependency
  9. container['database'] # => SmartCore::Container::ResolvingError
  10. # but you can fetch any depenendency type (internal containers and values) via #fetch
  11. container.fetch('database') # => SmartCore::Container (nested container)
  12. container.fetch('database.resolver') # => #<SomeDatabaseResolver:0x00007f0f0f1d6332>

runtime-level dependency/namespace registration

  1. container.namespace(:api) do
  2. register(:provider) { GoogleProvider } # without memoization
  3. end
  4. container.register('game_api', memoize: true) { 'overwatch' } # with memoization
  5. container['api.provider'] # => GoogleProvider
  6. container['game_api'] # => 'overwatch'

container keys (dependency names):

  1. # get dependnecy keys (only dependencies)
  2. container.keys
  3. # => result:
  4. [
  5. 'database.resolver',
  6. 'database.cache.memcached',
  7. 'database.cache.redis',
  8. 'logger',
  9. 'random'
  10. ]
  1. # get all keys (namespaces and dependencies)
  2. container.keys(all_variants: true)
  3. # => result:
  4. [
  5. 'database', # namespace
  6. 'database.resolver',
  7. 'database.cache', # namespace
  8. 'database.cache.memcached',
  9. 'database.cache.redis',
  10. 'logger',
  11. 'random'
  12. ]

key predicates

  • key?(key) - has dependency or namespace?
  • namespace?(path) - has namespace?
  • dependency?(path) - has dependency?
  • dependency?(path, memoized: true) - has memoized dependency?
  • dependency?(path, memoized: false) - has non-memoized dependency?
  1. container.key?('database') # => true
  2. container.key?('database.cache.memcached') # => true
  3. container.dependency?('database') # => false
  4. container.dependency?('database.resolver') # => true
  5. container.namespace?('database') # => true
  6. container.namespace?('database.resolver') # => false
  7. container.dependency?('database.resolver', memoized: true) # => true
  8. container.dependency?('database.resolver', memoized: false) # => false
  9. container.dependency?('random', memoized: true) # => false
  10. container.dependency?('random', memoized: false) # => true

state freeze

  • state freeze (#freeze!, .#frozen?):
  1. # documentation is coming;

reloading

  • reloading (#reload!):
  1. # documentation is coming;

hash tree

  • hash tree (#hash_tree, #hash_tree(resolve_dependencies: true)):
  1. # documentation is coming;

explicit class definition

  • SmartCore::Container.define - avoid explicit class definition (allows to create container instance from an anonymous container class immidietly):
  1. # - create from empty container class -
  2. AppContainer = SmartCore::Container.define do
  3. namespace :database do
  4. register(:logger) { Logger.new }
  5. end
  6. end # => an instance of Class<SmartCore::Container>
  7. AppContainer.resolve('database.logger') # => #<Logger:0x00007f5f0f2f0158>
  8. AppContainer['database.logger'] # => #<Logger:0x00007f5f0f2f0158>
  1. # - create from another container class with a custom sub-definitions -
  2. class BasicContainer < SmartCore::Container
  3. namespace(:api) do
  4. register(:client) { Kickbox.new }
  5. end
  6. end
  7. AppContainer = BasicContainer.define do
  8. register(:db_driver) { Sequel }
  9. end
  10. # --- or ---
  11. AppContainer = SmartCore::Container.define(BasicContainer) do
  12. register(:db_driver) { Sequel }
  13. end
  14. AppContainer['api.client'] # => #<Kickbox:0x00007f5f0f2f0158> (BasicContainer dependency)
  15. AppContainer['db_driver'] # => Sequel (AppContainer dependency)

subscribe to dependency changements

  • features and limitations:
    • you can subscribe only on container instances (on container instance changements);
    • at this moment only the full entity path patterns are supported (pattern-based pathes are not supported yet);
    • you can subscribe on namespace changements (when the full namespace is re-registered) and dependency changement (when some dependency has been changed);
    • #observe(path, &observer) => observer - subscribe a custom block to dependency changement events (your proc will be invoked with |path, container| attributes);
    • #unobserve(observer) - unsubscribe concrete observer from dependency observing (returns true (unsubscribed) or false (nothing to unsubscribe));
    • #clear_observers(entity_path = nil) - unsubscribe all observers from concrete path or from all pathes (nil parameters);
  • aliases:
    • #observe => #subscribe;
    • #unobserve => #unsubscribe;
    • #clear_observers => #clear_listeners;
  1. container = SmartCore::Container.define do
  2. namespace(:database) do
  3. register(:stats) { 'stat_db' }
  4. end
  5. end
  1. # observe entity change
  2. entity_observer = container.observe('database.stats') do |dependency_path, container|
  3. puts "changed => '#{container[dependency_path]}'"
  4. end
  5. # observe namespace change
  6. namespace_observer = container.observe('database') do |namespace_path, container|
  7. puts "changed => '#{namespace_path}'"
  8. end
  1. container.fetch('database').register('stats') = 'kek' # => invokes entity_observer and outputs "changed! => 'kek'"
  2. container.namespace('database') {} # => invoks namespace_observer and outputs "changed => 'database'"
  3. container.unobserve(observer) # unsubscribe entity_observer from dependency changement observing;
  4. container.clear_observers # unsubscribe all observers
  5. container.fetch('database').register('stats') = 'pek' # no one to listen this changement... :)
  6. container.namespace('database') {} # no one to listen this changement... :)

Roadmap

  • migrate to Github Actions;

  • convinient way to rebind registered dependnecies:

  1. # PoC
  2. container['dependency.path'] = 'pek' # simplest instant dependency registration without memoization
  3. # --- or/and ---
  4. container.rebind('dependency.path', memoize: true/false) { 'pek' } # bind with dynamic dependency registration
  5. container.rebind('dependency.path', memoize: true/false, 'pek') # bind with instant dependency registration
  • pattern-based pathes in dependency changement observing;
  1. container.observe('path.*') { puts 'kek!' } # subscribe to all changements in `path` namespace;
  • support for instant dependency registration:
  1. # common (dynamic) way:
  2. register('dependency_name') { dependency_value }
  3. # instant way:
  4. register('dependency_name', dependency_value)
  • support for memoization ignorance during dependency resolving:
  1. resolve('logger', :allocate) # Draft
  • container composition;

  • support for fallback block in .resolve operation (similar to Hash#fetch works);

  • inline temporary dependency switch:

  1. with(logger: Logger.new, db: DB.new) do
  2. # logger is a new logger
  3. # db is a new db
  4. end
  5. # out of block: logger is an old logger, db is an old db

Contributing

  • Fork it ( https://github.com/smart-rb/smart_container/fork )
  • Create your feature branch (git checkout -b feature/my-new-feature)
  • Commit your changes (git commit -am '[feature_context] Add some feature')
  • Push to the branch (git push origin feature/my-new-feature)
  • Create new Pull Request

License

Released under MIT License.

Supporting


Supported by Cado Labs

Authors

Rustam Ibragimov