Boost logo

Boost :

From: Richard Hodges (hodges.r_at_[hidden])
Date: 2021-02-21 07:48:07


On Sun, 21 Feb 2021 at 00:27, Krzysztof Jusiak via Boost <
boost_at_[hidden]> wrote:

> Let me try to explain DI (manual and automatic) via example.
>
> DI vs NO-DI is all about less coupling by injecting dependencies instead
> of.
> It's often referred as a Hollywood prince - Don't call us we will call you!
>
> Example
>
> struct coupled_no_di {
> void api() {
> d.call();
> }
> depedency d{singleton::get()};
> };
>
> - Coupled design (hard to test)
>
> struct not_coupled_di {
> not_coupled_di(dependency& d) : d_{d} {}
>
> void api() {
> d.call();
> }
> depedency& d;
> };
>
> - Much better - separation of business logic and object creation.
>
> But we can do even better by applying
> - Dependency Inversion (relay on abstractions and not concrete
> implementations)
> - Abstractions can be done in many different ways (type erasure, templates,
> abstract classes, etc.)
>
> template<DependencyConcept TDependency>
> struct not_coupled_di {
> not_coupled_di(TDependency& d) : d_{d} {}
>
> void api() {
> d.call();
> }
> TDependency& d;
> };
>
> The above is much better because
> - It's not coupled to any specific implementation
> - Can be changed for testing (mocks/fakes/stubs)
>
> Okay, now let's take at the main. A good design will apply a Composition
> Root (unique place in the app when dependencies are being created)
>
> int main() {
> my_depedency dependency{...};
> not_coupled_di di{dependency};
>
> di.api();
> }
>
> - The above is an example of manual DI which is cool already but may lead
> to what is called a Wiring Mess.
>
> Let's imagine that we have a big dependency tree because we are following
> SOLID principle and we especially apply the Single Responsibility Principle
> (we will have a lot of dependencies).
>
> int main() {
> my_dependency_1 d1{};
> my_dependency_2 d2{};
> my_dependency_3 d2{d1, d2};
> my_dependency_4 d3{d1, 42, d3};
> app a{d3, ...};
> ...
>
> // Order of the above is important and with bigger projects might be
> easily 10k LOC+
> }
>
> - Well, we can maintain the above if we want, but Automatic DI will let us
> actually focus on the business logic instead!
> - Any change in the constructors (a reference to shared_pointer, the order
> of parameters) will require us to change the above :(
>
> Boost.DI library can help with removing the Wiring Mess for us, though!
>
> int main() {
> auto injector = make_injector();
> app = create<app>(injector);
> return app.api();
> }
>
> Right now the benefits of using the framework instead of manual DI
> - If we change any constructor parameter or order of any dependency we
> DON't have to change anything with DI framework
>
> - Before
> not_coupled_di(TDependency& d);
>
> - Refactor
> not_coupled_di(std::shared_ptr<TDependency>);
>
> With manual DI the wiring has to be changed to pass the shared_ptr with
> Boost.DI we don't have to change the wiring code at all.
>
> Okay, but what about the polymorphism behaviour.
> Boost.DI allows a different type of polymorphism. One can inject templates,
> abstract classes, variant, type erasure etc.
> More can be found here -
> https://github.com/boost-ext/di/tree/cpp14/example/polymorphism.
>
> Why that's important? Because it's a better design and makes testing easier
> too.
> How to do it with Boost.DI?
>
> // configuration
> auto module = make_injector(
> bind<interface>.to<implementation> // I'm not going to go into details,
> but Boost.DI allows to inject TEMPLATES, abstract classes, etc...
> );
>
> // production, release
> int main() {
> app = create<app>(module);
> return app.api();
> }
>
> // end 2 end testing
> int main() {
> auto injector = di::make_injector(
> module(), // production wiring
> di::bind<interface>.to<mock> [override]
> );
> app = create<app>(module);
> return app.api();
> }
>
> - Great, we can test end 2 end with overriding some dependencies such as
> time, database, networking, logging etc...
> - We have a loosely coupled design!
> - We don't have to change ANYTHING in the wiring code when we change the
> dependency tree and/or any constructor parameters/order
>
> I hope that helps a bit?

Extremely helpful, thank you.

So in summary it’s a clever bag of arguments that are matched by type to
any constructor it’s applied to.

The motivating use case is to reduce the burden of maintaining the
dependencies of application objects.

Is that a succinct, if simplistic, summary?

>
> I also really encourage to take a quick look at which summaries concepts
> behind DI.
> - https://www.youtube.com/watch?v=yVogS4NbL6U
>
> Thanks, Kris
>
> On Sat, Feb 20, 2021 at 4:10 PM Peter Dimov via Boost <
> boost_at_[hidden]>
> wrote:
>
> > Andrzej Krzemienski wrote:
> > ...
> > > So, I want to apply the Dependency Injection philosophy. I get:
> > >
> > > class State
> > > {
> > > Rect area;
> > > Point location;
> > > // invariant: location is inside area;
> > > public:
> > > State(Rect a, Point loc) : area{a}, location{loc} {}
> > > };
> > >
> > > int main()
> > > {
> > > State s{Rect{{0, 0}, {2, 2}}, Point{1, 1}};
> > > }
> > > ```
> > >
> > > Now, I choose to use the DI-library (this is where I have troubles with
> > > understanding: why would I want to do that?).
> >
> > You are thinking in C++ (value semantics) but you need to be thinking in
> > Java (reference semantics, OOP, object DAGs.) Suppose you need to have a
> > logger object
> >
> > shared_ptr<ILogger> pl; // new FileLogger( log_file_name fn );
> >
> > and a database connector object (which uses a logger to log things)
> >
> > shared_ptr<IDbConn> pdb; // new MysqlDb( mysql_db_info mdi,
> > shared_ptr<ILogger> pl )
> >
> > and an abstract configuration query interface, that can use an .ini file,
> > or
> > a JSON file, or a database
> >
> > shared_ptr<IConfig> pc; // new DbConfig( shared_ptr<IDbConn> pdb,
> > shared_ptr<ILogger> pl );
> >
> > Now when you need to create the config query object, you need to give it
> a
> > database connector and a logger, and you also need to give the same
> logger
> > to the database connector. So with [NotYetBoost].DI you'll write
> something
> > like
> >
> > auto injector = make_injector(
> > bind<log_file_name>.to( "something.log" ),
> > bind<ILogger>.to<FileLogger>(),
> > bind<>.to( mysql_db_info{ ... } ),
> > bind<IDbConn>.to<MysqlDb>(),
> > bind<IConfig>.to<DbConfig>()
> > );
> >
> > auto cfg = injector.create<IConfig>();
> >
> > although I don't really know if this will compile or work. :-)
> >
> > Now imagine that same thing but with 10 objects, or 20 objects. Also
> > imagine
> > that your day to day vocabulary includes "business logic", "enterprise",
> > "UML" and "XML".
> >
> >
> > _______________________________________________
> > Unsubscribe & other changes:
> > http://lists.boost.org/mailman/listinfo.cgi/boost
> >
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
>

-- 
Richard Hodges
hodges.r_at_[hidden]
office: +442032898513
home: +376841522
mobile: +376380212

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk