Git Product home page Git Product logo

Comments (6)

poletti-marco avatar poletti-marco commented on May 22, 2024

If the set of keys is fixed at compile time, you could use annotated injection for this.
If not (as in the example above with a client ID) then you could have Fruit inject the map but then insert entries yourself when you want to.
When injecting a T& multiple times from the same injector, Fruit will always return the same instance.

If you have a per-process injector (as the first injector created in the server example) then you can have Fruit construct the singleton for you in that injector, instead of using a static var as above.
In production the 2 approaches are equivalent, but in tests the injected approach allows you to have a fresh singleton for each test case if you want (and it's probably a good idea), while in the static approach you'd have to either run each test case in a new process or have to reset the singleton between test cases (which would require exposing that impl detail potentially several layers of abstraction higher than it should be).

I would recommend against the static singleton approach unless you really can't use injection for it for some reason (and I can't imagine a good reason).

So you'd inject:

  • &CBusinessObjectBroker (wrapper around the map), and
  • std::function<std::unique_ptr<CStatement>()> so that you can inject things into CStatement if you want

I hope that helps, lmk if you have more questions.

from fruit.

IrisPeter avatar IrisPeter commented on May 22, 2024

@poletti-marco On the per-process injector which you say is the first injector created in the server example. Is that requestDispatcherNormalizedComponent on line 47 of server.cpp?

Or is it inside worker_thread_main?

The set of keys is created at runtime rather than compile time. I assume that to be able to have similar lifetime control to the GetStatement method described by me above, that I will again need to check the map to see if the key is present, and then if it isn't I need to get a CStatement::ptr using a Injector<CStatement>, perhaps one using the NormalizedComponent class, and then insert that into the map?

In our business objects ptr was a typedef for an in house intrusive pointer template class called counted_ptr which dealt with reference counts, for our new equivalent of CBusinessObjectBroker we will be using the std library, and so I think we need to use - std::function<std::shared_ptr<IStatement>(int)>;

Is the server example going to be the best one for me to be looking at to come up with a similar mechanism that is in our CBusinessObjectBroker?

Your first bullet point "So you'd inject &CBusinessObjectBroker (wrapper around the map)" - I assume you mean:

fruit::Injector<CBusinessObjectBroker> injector(getBusinessObjectBrokerComponent);
CBusinessObjectBroker::Ptr brokerObjectPtr(injector);

correct?


The set of keys is created at runtime rather than compile time.> > Or is it inside worker_thread_main?

This is surprising. Then maybe those objects represent individual data items?

The majority of our business objects are loaded from the SQL Database and are connected to clients registered with the system (i.e. stored in a database table, and loadable by the user via a client selector dialog) which is why the objects are defined at runtime rather than compile time.

I need to preserve the existing lifetime functionality of each business object, and so the CBusinessObjectBroker will need to maintain the same mechanism for handing out each object that the current object supports (See Object support further down).

Ptr typdef

In the existing code base the majority of business objects have a typdef counted_ptr<CObjectName> CObjectName_ptr; in the parent namespace that the business objects class resided in.

I noticed that for one of these objects the writer of the object instead placed a typedef within the class itself to the counted_ptr<T> called ptr, I decided to do the same but for every object I duplicate from the original business objects library I would call the typedef ::Ptr, and so CObjectName_ptr becomes CObjectName::ptr

I've introduced a template class to provide the relevant typedefs using CTRP, in the case of CBusinessObjectBroker::Ptr is the same as CBusinessObjectBroker*, the broker will be handing out std::shared_ptr<T>

Object Support

CBusinessObjectBroker either a) hands out objects either directly from member variables within CBusinessObjectBroker, or b) from maps, where the keys are classes encapsulating database keys, sometimes these keys are just ints representing tax years.

In the original Business Tax Objects library we were handing out objects using our own internal intrusive pointer type called counted_ptr, which was a template class that was used similarly to shared_ptr, but whose history dates back to some time before 2007.

In the case of a) the member variables where counted_ptr, and so the objects are constructed on demand, b) the maps again store counted_ptr and objects are either handed out from the map if they exist already or constructed on demand and stored in the relevant map, before being handed out.

For every business object that currently hands out of these counted_ptr<T> intrusive pointers, I will replace these by std::shared_ptr<T>

The CBusinessObjectBroker is created as a singleton and is retrieved by CTaxBusinessObjectBroker& CTaxBusinessObjectBroker::Instance()
and is what we use to create all our business objects.


Your first bullet point "So you'd inject &CBusinessObjectBroker (wrapper around the map)" - I assume you mean:

I don't know what CBusinessObjectBroker::Ptr is, I would have expected CBusinessObjectBroker& there. And the injector would probably have other types too (CBusinessObjectBroker may not be one of the toplevel types at all). But maybe that was an intentional simplification as an example.

From your reply, I think you are saying the same as me when you said:

while using some other mechanism (e.g. a map or some other data structure that might live inside one of the object that Fruit manages) to hold any other objects that are more dynamic.

which seems similar to when I said:

The set of keys is created at runtime rather than compile time. I assume that to be able to have similar lifetime control to the GetStatement method described by me above, that I will again need to check the map to see if the key is present, and then if it isn't I need to get a CStatement::ptr using a Injector, perhaps one using the NormalizedComponent class, and then insert that into the map?


You could still use Fruit to inject a factory to create those objects, that would return e.g. a unique_ptr of that object but then leave it to you to store those in a map/etc and cache/reuse objects when desired.

So this tutorial is probably more relevant than the server one for this: https://github.com/google/fruit/wiki/tutorial:-assisted-injection And this part of the reference documentation: https://github.com/google/fruit/wiki/quick-reference#factories-and-assisted-injection

Could you expand on this, none of the examples seem to use registerFactory, the Wiki page declares getMyClassComponent, but then doesn't seem to show its usage?

Oh I see in component.h shows what seems to be missing from the Wiki page, that is creating the injector that is then passed to the factory declaration

Injector<std::function<std::unique_ptr<MyClass>(int)>> injector(getMyClassComponent);

std::function<std::unique_ptr<MyClass>(int)> myClassFactory(injector);
std::unique_ptr<MyClass> x = myClassFactory(15);

However I'm still a little confused, why is .RegisterFactory used in the above example, it looks very similar to the ScalerFactory in the scaling doubles example, which didn't seem to need RegisterFactory, is it because the ScalerFactory just takes non injected parameters whereas MyClass also needs a FOO* injected into it?

In my message on Friday I mentioned NormalizedComponent it seems to be some sort of optimisation for when using factories that are expensive in some way, now that you've pointed me away from the server example for my use case, does that mean this is not going to be of use to me?

Then again I also see the use of NormalizedComponent in the testing example. What is the criteria should I look for on deciding whether I need to use this?

Looking in the Testing example I see you have a createInjector method, whether we are going to be using NormalizedComponent or not. What are the criteria I should look for when deciding whether I need have a createInjector method?

The cached_greeter_test[_with_normalized_component].cpp example is very helpful as I see that it shows me how I will replace our business objects standard loading from DB with loading from JSON, in the same way that getFakeKeyValueStorageComponent swaps out the standard behaviour of the storage component with one that uses a std::map to simulate what the real component will be using when under test.

from fruit.

poletti-marco avatar poletti-marco commented on May 22, 2024

On the per-process injector which you say is the first injector created in the server example. Is that requestDispatcherNormalizedComponent on line 47 of server.cpp?
Or is it inside worker_thread_main?

Oh it looks like there's a single injector there, per-request.
If you wanted a per-process injector you could define one e.g. in main or in a closely related function that is called only once.

The set of keys is created at runtime rather than compile time.

This is surprising.
Then maybe those objects represent individual data items?
I'd recommend using Fruit/DI to compose the modules of your system and the more static components that you can describe with a fixed set of keys, while using some other mechanism (e.g. a map or some other data structure that might live inside one of the object that Fruit manages) to hold any other objects that are more dynamic.

You could still use Fruit to inject a factory to create those objects, that would return e.g. a unique_ptr of that object but then leave it to you to store those in a map/etc and cache/reuse objects when desired.

So this tutorial is probably more relevant than the server one for this: https://github.com/google/fruit/wiki/tutorial:-assisted-injection
And this part of the reference documentation: https://github.com/google/fruit/wiki/quick-reference#factories-and-assisted-injection

Your first bullet point "So you'd inject &CBusinessObjectBroker (wrapper around the map)" - I assume you mean:

I don't know what CBusinessObjectBroker::Ptr is, I would have expected CBusinessObjectBroker& there.
And the injector would probably have other types too (CBusinessObjectBroker may not be one of the toplevel types at all). But maybe that was an intentional simplification as an example.

from fruit.

poletti-marco avatar poletti-marco commented on May 22, 2024

However I'm still a little confused, why is .RegisterFactory used in the above example, it looks very similar to the ScalerFactory in the scaling doubles example, which didn't seem to need RegisterFactory, is it because the ScalerFactory just takes non injected parameters whereas MyClass also needs a FOO* injected into it?

If the std::function just needs to call a constructor with a mix of injected params + the ones provided to the std::function's operator(), then you can use the ASSISTED(...) approach, it's simpler.
registerFactory is a generalization of that that allows to call arbitrary code (not just the constructor of the type returned by the factory) when the std::function is called.

Then again I also see the use of NormalizedComponent in the testing example. What is the criteria should I look for on deciding whether I need to use this?

NormalizedComponent is useful if you want to create many independent injectors using the same get*Component function.
Using that you can move some of the cost of the injector creation to the process startup, and share it across all injectors that are created.
If you're going to create just 1 injector there's no point using this.

E.g. this is appropriate for servers where you might want to isolate the handling of each request so that each request uses a separate injector (with separate instances of all objects).
From what you said, this doesn't seem to be the case here?
Or at least, this is orthogonal to the discussion above on how to model CBusinessObjectBroker, since AFAICT for that the goal is to share instances so it sounds like you want only 1 CBusinessObjectBroker.

Looking in the Testing example I see you have a createInjector method, whether we are going to be using NormalizedComponent or not. What are the criteria I should look for when deciding whether I need have a createInjector method?

createInjector is not a special name, the testing example just happens to define a helper function with that name to call that in all tests, to avoid duplicating the code in all of them. You could call it createMyInjector or inline it, it doesn't matter.

This is not like the get*Component functions where you do need a function returning Component and you can't just inline that.

from fruit.

IrisPeter avatar IrisPeter commented on May 22, 2024

Sorry for simplicity's sake I probably should have said:

Your first bullet point "So you'd inject &CBusinessObjectBroker (wrapper around the map)" - I assume by that you mean:

fruit::Injector<CBusinessObjectBroker> injector(getBusinessObjectBrokerComponent);
CBusinessObjectBroker* brokerObjectPtr(injector);

correct?

On your other question:

And the injector would probably have other types too (CBusinessObjectBroker may not be one of the toplevel types at all). But maybe that was an intentional simplification as an example.

No in this particular case I did mean what I said, in that there is only one CBusinessObjectBroker it was not a simplification, but yes as described in my previous message whilst CBusinessObjectBroker is a top level type, it is mostly the mechanism to get a specific business object (non top-level types in your taxonomy) whilst most of the time they are provided by the broker each object has a public static method Create which will return a counted_ptr. Instead of counted_ptr I will be using std::shared_ptr as I think its better to use smart pointers from the standard library rather than these internal types.

from fruit.

poletti-marco avatar poletti-marco commented on May 22, 2024

Hi, is there some remaining question here?
The discussion got quite long, if you're still waiting for an answer on something can you please mention that here?
Or if your questions here were addressed please close this.

from fruit.

Related Issues (20)

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.