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

Path to kangaru 5 #101

Open
gracicot opened this issue Mar 10, 2021 · 11 comments
Open

Path to kangaru 5 #101

gracicot opened this issue Mar 10, 2021 · 11 comments
Labels
Milestone

Comments

@gracicot
Copy link
Owner

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

  • All services should be possible to define using the service map. Right now, autocall and supplied services can't really be done in a practical manner using the service map definition.
  • The API should prioritize the service map. services defined as a struct should be special cases.
  • C++17
  • The container should be a template. The container "allocator" should be possible to change and allow statically defined finite container to support zero overhead.
  • constexpr containers.
  • Simplification to the injection model, that includes invoke and the construct function.

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:

  • If we go full service map, do we still need the construct function in services?
  • If we go full service map, should we allow simply classes to be used as service in the container instead of the service definition?

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 gracicot added this to the 5.0 milestone Mar 10, 2021
@gracicot
Copy link
Owner Author

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
Copy link
Owner Author

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

@abeimler
Copy link

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
Copy link
Owner Author

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
Copy link
Owner Author

gracicot commented Jul 7, 2023

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
Copy link
Contributor

wopss commented Feb 11, 2024

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
Copy link
Owner Author

@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
Copy link
Contributor

wopss commented Feb 11, 2024

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:

  • Suppose I have two abstract services: IFirstInterface and ISecondInterface.
  • I'm creating two services: ServiceA, which inherits from IFirstInterface, and ServiceB, which inherits from ISecondInterface. ServiceB has a constructor dependency on IFirstInterface.
  • If I add ServiceA using emplace first, followed by ServiceB, everything works fine. But if I add ServiceB first and then ServiceA, an exception occurs because ServiceB can't resolve its dependencies.

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
Copy link
Owner Author

gracicot commented Feb 12, 2024

@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
Copy link
Contributor

wopss commented Feb 12, 2024

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 #101 (comment).

@gracicot
Copy link
Owner Author

gracicot commented Nov 14, 2024

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

  • autocall
  • fork/merge/rebase
  • abstract services
  • lazy services
  • generator services
  • defaulted abstract services (not planned)
  • override range (not planned)
  • named service map (not planned)
  • dynamic supplied service (unknown)

Many new goodies have been introduced:

  • static external/supplied services
  • no need for dynamic allocation when not needed
  • fully constexpr
  • composable injectors
  • composable containers (sources)
  • function parameter patterns
  • no more service definition classes

Since it's a full rewrite, many things are different or missing. The new implementation is actually much much more flexible and composable. Adding new feature or creating your own container should be not that difficult actually.

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.

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

3 participants