Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeclarativeContainer need to be define in order ? #207

Closed
wiryonolau opened this issue Nov 6, 2018 · 10 comments
Closed

DeclarativeContainer need to be define in order ? #207

wiryonolau opened this issue Nov 6, 2018 · 10 comments
Assignees
Labels

Comments

@wiryonolau
Copy link

Hi,
I'm grouping provider to Service, Repository and Adapter, the group provider is extending DeclarativeContainer.

I have this dependency:
"Service A" require "Repository A"
"Repository A" require "Adapter A" and "Service B"

"Repository A" cannot be created because Repository class is define before Service class, is this how it suppose to work ? Is it possible to handle this using DynamicDeclarative

I'm using Ubuntu 16.04, Python 3.5.2, dependency_injector 3.12.4

@wiryonolau wiryonolau changed the title DeclarativeContainer need to define in order ? DeclarativeContainer need to be define in order ? Nov 6, 2018
@wiryonolau
Copy link
Author

Ah I need to use DependenciesContainer, should put this in docs.

@wiryonolau wiryonolau reopened this Nov 6, 2018
@wiryonolau
Copy link
Author

wiryonolau commented Nov 6, 2018

Ok, it's quite problematic in my scenario. Is there a way to make it all late binding ?

class Core(containers.DeclarativeContainer):
    config = providers.Configuration('config')
    logger = providers.Singleton(logging.Logger)

class Service(containers.DeclarativeContainer):
    site = providers.Singleton(service.SiteService, site_repo=Repository.site)
    queue = providers.Singleton(service.QueueService)

class Adapter(containers.DeclarativeContainer):
    tinydb = providers.Singleton(adapter.TinyDBAdapter, db_path=Core.config.default.db_path)

class Repository(containers.DeclarativeContainer):
    site = providers.Singleton(repository.SiteRepository, adapter=Adapter.tinydb, q=Service.queue)

@rmk135 rmk135 self-assigned this Nov 7, 2018
@rmk135 rmk135 added the question label Nov 7, 2018
@rmk135
Copy link
Member

rmk135 commented Nov 7, 2018

Hi @wiryonolau ,

Thanks for the question! It's really interesting, I will try to propose a solution from my side in next few days. It looks like something that could be handled by existing functionality, but I will need some time to brainstorm it from all sides and try to found out something elegant.

PS: In general, it looks like the issue you're facing is that your architecture has circular dependencies in terms of component types: Service <-> Repository. If it's possible I would recommend you to try to break this bidirectional linking to single-directional Service -> Repository. This change should make your architecture much simpler for further understanding, scaling, etc... I know that sometimes there is no way to avoid bidirectional linking, but just wanted to highlight that if there is any single chance to make it - then it makes sense to make it, imho.

Thanks,
Roman

@wiryonolau
Copy link
Author

Thanks, My understanding is that Service is kind of a default category, unless they can be further classified to specific category like Repository... I used to develop using PHP Zend Framework when most of the code is inside a service.

@wiryonolau
Copy link
Author

wiryonolau commented Nov 9, 2018

This doesn't work, config not updated.

In this scenario I need to update config from user input, too make it simple I substitute with dict directly.

# container.py
import service
import storage
import adapter
import repository

class Core(containers.DeclarativeContainer):
    config = providers.Configuration('config')

class Storage(containers.DeclarativeContainer):
    queue = providers.Singleton(storage.QueueStorage)

class Adapter(containers.DeclarativeContainer):
   ######################################
   ### Not updated, value always None ###
   print(Core.config.default.db_path()) 
   ######################################

    tinydb = providers.Singleton(
        adapter.TinyDBAdapter, db_path=Core.config.default.db_path)

class Repository(containers.DeclarativeContainer):
    site = providers.Singleton(
        repository.SiteRepository, adapter=Adapter.tinydb, _queue=Storage.queue)

class Service(containers.DeclarativeContainer):
    config = providers.Singleton(
        service.ConfigService, config=Core.config.provider)

# service.py
class ConfigService:
  def __init__(self, config):
    self._config = config

  def build(self):
    self._config.override({"default": {"db_path": "~/test"}})

# __main__.py
from container import Service

config = Service.config()
config.build()

site = Service.site()

@wiryonolau
Copy link
Author

wiryonolau commented Dec 20, 2020

Hi, I create my own Dependency Injection library to show you what I means https://github.com/wiryonolau/python-easydi.

I mainly wrap the registered variable with a function, to make it not creating the object on registration but only register the class path for both the factory and the dependency. This way there is no need to think about the order when declaring container.

My library is not optimize but you should be able to get the general idea.

PS: In general, it looks like the issue you're facing is that your architecture has circular dependencies in terms of component types: Service <-> Repository.

It's not a circular dependencies that I need, that is dangerous. It's more like lazy loading, you define everything regardless order, if it's not found the container will throw an error.

With this approach I think we can have a declarative container using dictionary, instead of using variable directly.

@rmk135
Copy link
Member

rmk135 commented Dec 20, 2020

Hi @wiryonolau , great, congratulation on easydi. That looks interesting. I will take a closer and look and may be borrow some ideas to incorporate into Dependency Injector. Are you ok with this?

@wiryonolau
Copy link
Author

Hi no problem. I just use it for my own project.

@rmk135
Copy link
Member

rmk135 commented Feb 18, 2021

Hi @wiryonolau ,

I've reviewed easydi. Great job! I highlighted aliases and dependency groups ideas. I had a conversation with one person from Scala world who does Scala dependency injection framework. He told me about tag-based dependency resolution principle that they do. That's cool stuff. Something similar to what could be done with groups and aliases. May be these features will appear in Dependency Injector one day.

As of the lazy vs ordered declaration style, I designed Dependency Injector to avoid string identifiers. That was done with opened eyes. Having reference identification requires ordered declaration. To make Dependency Injector support laziness, some features were introduced since that post was originated. Here is how your example could look like now:

"""Containers module."""

from dependency_injector import containers, providers

from .services import ConfigService


class Core(containers.DeclarativeContainer):
    config = providers.Configuration('config')


class Storage(containers.DeclarativeContainer):
    queue = providers.Singleton(lambda: 'Some storage')


class Adapter(containers.DeclarativeContainer):
    core = providers.DependenciesContainer(config=providers.Configuration())
    tinydb = providers.Singleton(
        lambda db_path: f'DB Path=[{db_path}]',
        db_path=core.config.default.db_path,
    )


class Repository(containers.DeclarativeContainer):
    adapter = providers.DependenciesContainer()
    storage = providers.DependenciesContainer()
    site = providers.Singleton(
        lambda adapter, queue: f'Adapter=[{adapter}], queue=[{queue}]',
        adapter=adapter.tinydb,
        queue=storage.queue,
    )


class Service(containers.DeclarativeContainer):
    core = providers.DependenciesContainer()
    config = providers.Singleton(ConfigService, core.config.provider)


class Application(containers.DeclarativeContainer):

    core = providers.Container(Core)
    storage = providers.Container(Storage)
    adapter = providers.Container(Adapter, core=core)
    repository = providers.Container(Repository, adapter=adapter, storage=storage)
    service = providers.Container(Service, core=core)
    
   
if __name__ == '__main__':
    application = Application()
    config = application.service.config()
    config.build()

    print(application.repository.site())
    # Output: Adapter=[DB Path=[~/test]], queue=[Some storage]

You can find the code here: https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers-runtime-overriding

I guess you will keep using easydi anyway, but just in case... :)
I'm closing the issue for now. Feel free to comment though. Appreciate your feedback.

@rmk135 rmk135 closed this as completed Feb 18, 2021
@wiryonolau
Copy link
Author

Hi, thanks for the feedback I'll test it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants