My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
ProposalForNewableVsInjectable  
How the injector will distinguish between newable and injectable types
Proposal
Updated Nov 30, 2009 by aeagle22206

Introduction

This feature is motivated by Misko's blog post, To new or not to new (http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/)

Our proposal in Noop is that a class should ideally either be:

  • a "service", or "injectable", constructed entirely from things the injector knows how to provide, and then provided by the injector or
  • a "data", or "newable", made from objects that another class should provide at runtime, and not provided by the injector
Mixing them is problematic, but necessary.

When a data needs a service

For example, a Range object may want a Logger, which should be provided by the injector, yet the initial values of the range are quite newable. And requesting injection of the Range type doesn't make sense, unless it is aliased. Here is an example syntax, which uses different brackets to denote the injectable properties from the newable ones:

class Range[Logger logger](Int start, Int end) {}
...
Range r = Range(1,3);

This is conceptually similar to a factory, which can be created by a DI framework like Spring or Guice, and then you'd call a create method passing the newable parts, so that the product of the factory may have both newable and injectable dependencies. It's also similar to Assisted Injection in Guice2.

When a data becomes a service

The String type would usually be newable. However, when aliased to Username, it's natural that a request-scoped injector could provide an instance. The alias itself provides the clue: String -> Username is a good indication that the newable type String is aliased to an injectable type Username.

Alternate proposal with named parameters

It's troubling that the square/round paren syntax forces you to decide how your class will be created later. Maybe you think that some parameter will only be provided by the creating class, then realize it can be provided by a request-scoped injector. If we mix the newable and injectable parameters in the same list, we'll need named parameters so you can say which of your arguments should be assigned to which parameters:

class BankServiceImpl(String name, DbConnection c) {
  // some code
}

class Foo() {

  Void myMethod() {
    BankServiceImpl.new(name -> "myName"); // let the injector fill in the DbConnection
  }
}

Even newables are mockable

We want to guarantee that there is always a seam between two classes, so that they can be tested in isolation. This should even be true for newable types.

// in Foo.noop
class Foo() {
  Void method() {
    email = Email.new("alexeagle@google.com", "How's it going?", "Just checking in, thanks");
    email.addAttachment(attachment); // there's some nasty MIME encoding under here, let's say
  }
}
// in FooTest.noop
test {
  Email -> MockEmail;
  Foo.new().method(); // doesn't do the real attachment logic
}

So, this means that every object instantiation is handled by the injector. But, in the email class, the injector needs to just accept the user's parameters.

class Email(String to, String subject, String body) {
}

In the compiler, we will probably want to optimize these calls to be an actual new operation.

Determining whether a type is newable or injectable

In this case, we could bootstrap our knowledge by noting that surely String is newable. Since Email takes only newable parameters, Email is newable as well.

Types we know to be newable: String, Int, Array, Date, Float, Map, Enum, etc

Another option is to look at the parameter list, in the square/round paran syntax. A class with only square brackets is injectable. If there are round parens, it is newable.

Comments?

Here is a thread with some discussion about this topic: http://groups.google.com/group/noop/browse_thread/thread/f03fa72c9545e5c5

Comment by project member christia...@gmail.com, Sep 10, 2009

We could tell if we would the class with a keyword creatable, injectable, or interface (can be one and only one of the three.

interface BankService {
  WithdrawalTransaction withdraw(Money m);
}

newable Money(Float amount, Currency currency) {
   ... 
}

injectable BankServiceImpl {
  WithdrawalTransaction withdraw(Money m) {
    ...
  }
}
Comment by project member christia...@gmail.com, Sep 10, 2009

The use of interface and class may be just java-isms we have to jettison, instead using interface, injectable, newable... or maybe contract, injectable, newable

Comment by n.dasriaux@gmail.com, Sep 18, 2009

This shares strong resemblance with what you find in C# and C++ between class keyword and struct.

- class keyword would then allow to define injectables (class types) and would accept class types and struct types as fields.

- struct keyword would allow to define newables (struct types) and would only accept struct types as fields.

Comment by project member christia...@gmail.com, Sep 18, 2009

Similar, though with structs, you don't have method declarations, or mutators/accessors. Newables would be first-class objects like Injectables, but they wouldn't be governed by the Injector/Dependency-Injection Container.

Unlike C#, they aren't kept on the stack and quickly garbage collected either - they would behave just like injectables in this regard.

Comment by david...@gmail.com, Sep 22, 2009

Do we really need to mix "injectables" and "newables"? (I always call them "services" and "domain objects" because thats how it almost always breaks down but its pretty telling that the concept is so well known that everybody has their own term for it but no one has addressed it!). I've always seen the problem this way...

Dependency Injection and new aren't inherently different. Dependency injection is saying 'you can get a instance of an object by asking me for it' and the DI framework will handle the request however it chooses. Sometimes, we tell the framework, "whenever anyone asks for an instance of class C, give them this (singleton) instance". This is inherently part of construction but it doesn't have to be part of a constructor. We give 'new' a type and a list of (constructor) parameters and tell it 'now give me an object of the requested type with that info'. Only part of that process has to be the 'constructor'. DI with AOP looks exactly like this.

The @Inject annotation mechanism is always far more readable than constructor parameter injection and it won't get in the way of passing newables. Its only drawback, IMHO, is that its not quite as clear what is used in object construction if injectables arent constructor parameters, but this can be fixed with syntax or a code standard.

To be clear, here's your example with a possible syntax (off the top of my head so there are probably better ways):

/* This is how I customize my injector */ 
Injector.addSingleton(new BankService()); // use singleton rules when newing BankService (ie always return this instance) 
Injector.addPrototype(new ATM()); // use Prototype rules when newing ATMs (ie create a new one).

/* This is how I create an instance of an email that needs a BankService for some reason :-) */ 
class EMail(String to) 
{
    @Injectable BankService bankService; 
}

new EMail("sofat@yourmom.com"); // I already told new how to get a BankService so it just needs a string.

If I wanted to customize the BankService? at the time I call new EMail (which would be rare and might be a bad idea) then I could give new rules to 'new' for generating a BankService? OR we could allow a different notation like

new EMail<myOtherBankServiceInstance>("sofat@yourmom.com"); // Pretty crappy notation but you get the idea

(Ive got a better idea for this below)

FYI, I actually kind of like the idea of replacing Injector with New in my above examples so they become:

New.addSingleton(new BankService()); New.addPrototype(new ATM());

This makes clear the fact that dependency injection is just overloading New's behavior (New being like an Object in Scala parlance). Of course, if DI is going to be statically checked instead of done at run time (which I agree is wise) then this would have to be done a little differently but the general idea remains.


Again, what if I want to sometimes inject a parameter and sometimes pass it? Since the only difference between injection and parameter passing is syntax, this should always be allowed but it doesn't have to be 'clean' since it should be discouraged. If Noop DI ends up looking like PicoContainer?, which I hope it does, then the problem boils down to finding an efficient and expressive way of saying that we need to create a new container that is a child of the "current" container (also known as "New") and change some of its contents. This is cleaner than adding another function call notation like I mentioned above. I think it can be made to work pretty simply.


Sign in to add a comment
Powered by Google Project Hosting