gracicot / kangaru

🦘 A dependency injection container for C++11, C++14 and later
MIT License
502 stars 40 forks source link

Path to kangaru 5 #101

Open gracicot opened 3 years ago

gracicot commented 3 years ago

Here I simply want to discuss what we want for kangaru 5. There are a few points I want to see in kangaru 5:

I also want a more "conceptified" set of checks. That should also simplify error messages, which simply don't work anymore.

Now, there are a few points that raises questions:

There may be other questions. If you have suggestion or feedback, please write a comment here. I want to gather as much ideas and improvement for the version 5 as possible.

gracicot commented 2 years ago

In version 5, I want to go much further and expose all the guts. Let me explain.

Instead of having one container, I want to expose injection building blocks. Then, implement the container above those building blocks.

This will enable much more than what kangaru is doing right now. For example, being constexpr compatible, having a zero overhead interface, support for injection contexts, composing containers together and much more.

I will likely require C++17 and more recent compiler.

gracicot commented 1 year ago

I haven't gave up! I'm just quite busy there days. I plan to create the branch for version 5 soon :)

abeimler commented 1 year ago

if you can squeeze this in, an expected (C++23) for Supplied Services would be nice (so I don't rely on exceptions).

idk if it's possible but maybe you can harden the interface by returning an expected<T, NotFoundError> when the service is_supplied_service or is_abstract_service.

(I'm sure this can mess up the dependencies and it needs to be returned upstream if one dependency is not suppliable)

Pretty sure there are a lot of expected implementations (make sure there are supporting constexpr):

gracicot commented 1 year ago

Honestly the exceptions were just a workaround because the way I implemented supplied services requires deferring check to runtime. We'll see how kangaru 5 evolves, but I will most likely add a way to make runtime validation do something like that. My plan is that you could build your own container that acts how you need to using easy to understand building blocks.

Note that technically, you could make a service of excepted<T, NotFoundError> today in kangaru 4 using custom service definition. You can hook into the construction and injection mechanisms. Make it not supplied and make it hold an kgr::optional of your service. When that service is constructed by the container, leave it empty and make forward return a NotFoundError. You can then replace the sevice using container.replace but construct the replacement with a filled optional. The downside though is that this service is always gonna be injected as a excepted, but native support from kangaru would also do that.

The only problem is that custom service definitions are a super handy feature but also really complicated and hard to use, this is one point I want to make easier for kangaru 5.

gracicot commented 1 year ago

Okay, small update. I implemented a way to add injection metadata to types. For example, we can tag a type so kangaru know it's safe to default construct it.

This is great. Kangaru 5 will mostly work with normal types, then I'll implement service definition on top of it. The big difference is that service definition will be optional, and even though kangaru 5 will have containers, users will be able to implement their own containers or injection schemes.

Right now to express the metadata you can either declare friend function in the scope of a type or specialize a type trait.

WopsS commented 9 months ago

How will kangaru 5 handle service instantiation? Currently, when emplace or service is called, the service instance is created immediately. While kgr::lazy exists, it unfortunately lacks support for custom parameters and you need to specify it manually. Another annoying case is that if you have abstract service dependencies, the order in which you add them matters.

For the first case, it would be nice to have a feature similar to .NET Core, where a function could be passed to service or emplace to construct the service with captured variables.

gracicot commented 9 months ago

@WopsS In kangaru 5 there will not be "one" way to instantiate your services. You'll simply build the container that suits your needs. In many of my project, I simply don't need polymorphic services, so I'm gonna compose the container building blocks together in a way that don't support polymorphic. If you need services to be provided by a specific function, or a specific class then the hooks are gonna be there to make a container that reach for those.

Another annoying case is that if you have abstract service dependencies, the order in which you add them matters.

I honestly have no idea how to solve that, and I never used a framework where this wasn't a problem. It was either the first one wins, or the last one wins in the case where many classes implemented the same interface. Other framework that "didn't" have the problem simply threw an exception when your configuration could be conflicting.

Maybe you have a better idea than me how to solve this, as I don't use interfaces and inheritance or polymorphism at all in most of my code.

WopsS commented 9 months ago

It sounds like a good idea. Do you have any examples related to Kangaru 5 for that?

I honestly have no idea how to solve that, and I never used a framework where this wasn't a problem. It was either the first one wins, or the last one wins in the case where many classes implemented the same interface. Other framework that "didn't" have the problem simply threw an exception when your configuration could be conflicting.

Sorry, I didn't provide enough information. The scenario you described is fine with me; I don't have any concerns about services implementing the same interface and replacing each other.

However, what I meant to address is the following situation:

An example describing my issue:

struct IFirstInterface
{
    IFirstInterface() = default;
    virtual ~IFirstInterface() = default;
};

struct FirstInterfaceService
    : kgr::abstract_service<IFirstInterface>
{
};

struct ISecondInterface
{
    ISecondInterface() = default;
    virtual ~ISecondInterface() = default;
};

struct SecondInterfaceService
    : kgr::abstract_service<ISecondInterface>
{
};

struct ServiceA : public IFirstInterface
{
    ServiceA() = default;
    ~ServiceA() = default;
};

struct ServiceAService
    : kgr::single_service<ServiceA>
    , kgr::overrides<FirstInterfaceService>
{
};

struct ServiceB : public ISecondInterface
{
    ServiceB(const IFirstInterface& service)
        : m_service(service)
    {

    }

    ~ServiceB() = default;

private:
    const IFirstInterface& m_service;
};

struct ServiceBService
    : kgr::single_service<ServiceB, kgr::dependency<FirstInterfaceService>>
    , kgr::overrides<SecondInterfaceService>
{
};

int main()
{
    {
        // This works, me happy.
        kgr::container m_services;
        m_services.emplace<ServiceAService>();
        m_services.emplace<ServiceBService>();
    }

    {
        // This doesn't work, exception thrown :(
        kgr::container m_services;
        m_services.emplace<ServiceBService>();
        m_services.emplace<ServiceAService>();
    }

    return 0;
}
gracicot commented 9 months ago

@WopsS I see. I had another user that wanted abstract services but to be able to provide a callback that created the instance for it. That would fix this problem. It's not impossible in kangaru 4 today, just quite convoluted to achieve. I think I'll open an issue for this one, it wouldn't be that hard to add in.

WopsS commented 9 months ago

In addition to the specific you are mentioning, doing lazy initialization of the service by default (or a having a building block doing this) would be a nice addition, which would solve the example in https://github.com/gracicot/kangaru/issues/101#issuecomment-1937893897.

gracicot commented 2 weeks ago

Kangaru 5 now have a basic container that is functional, but still lacks functionalities. Here's a list of all missing features:

However, many new goodies have been introduced:

I plan to create a kangaru 5 version of the kangaru 4 interface so that migration is not too difficult.

I'll keep posting about the progress here.