[wiki.noop] push by aeagle22206 - Rename the dependency injection proposal, add the square-brackets-for-... on 2009-11-30 15:29 GMT

1 view
Skip to first unread message

no...@googlecode.com

unread,
Nov 30, 2009, 10:29:44 AM11/30/09
to noop-c...@googlegroups.com
Revision: 23d7ac5d04
Author: Alex Eagle <alex...@google.com>
Date: Mon Nov 30 07:29:09 2009
Log: Rename the dependency injection proposal, add the
square-brackets-for-injectable idea, and talk about running buildr from
head.
http://code.google.com/p/noop/source/detail?repo=wiki&r=23d7ac5d04

Added:
/ProposalForDependencyInjection.wiki
Deleted:
/PropositionForScopes.wiki
Modified:
/DeveloperRoadmap.wiki
/ForDevelopers.wiki
/ProposalForNewableVsInjectable.wiki

=======================================
--- /dev/null
+++ /ProposalForDependencyInjection.wiki Mon Nov 30 07:29:09 2009
@@ -0,0 +1,114 @@
+#summary Dependency Injection in Noop
+#labels proposal
+
+Dependency injection is built into Noop. This means that any class may
request injection of fields, and is guaranteed at runtime that instances of
the class will be created by an injector. We provide some compile-time
checks on the safety of the bindings, and make it easy to bind types to
implementations. Following is a proposition to handle binding cleanly by a
combination of pre-defined scopes, as well as the introduction of the
{{{binding}}} keyword and a binding operator `->` to solve this problem.
+
+= Concepts =
+An *injection request* is a syntax used in declaring a class which
indicates that some properties should be set by an injector. A class may
have both injected and user-provided properties. Here is an example syntax:
+`class Account[BankService service](Int initialBalance) {}`
+The square brackets indicate that the injector should provide an
appropriate instance of the BankService type, and set it as the initial
value of a property on Account named service. The parentheses indicate that
the initialBalance property cannot be set by the injector, rather, the code
that creates the Account instance will provide the value.
+
+A *!TypeLiteral* is an expression that refers to a Type. For example, the
String type can be referenced with the !TypeLiteral `String`. This is
roughly equivalent to `String.class` in Java for non-generic types. For
generic types, the !TypeLiteral represents the full type information, such
as `List<String>` rather than a class literal which can only represent
`List`. (note, noop doesn't have generic types yet)
+
+A *binding* is a pair consisting of a !TypeLiteral and an expression. The
injector uses the binding so that when a request is made for injection of a
particular type, the injector will know how to create an instance of that
type. Noop has a binding operator, `->`, used to declare a binding.
+
+Take a class defined as `MyClass(Apple a) {}`:
+ * `Apple -> GreenApple` tells the injector to supply an instance
of !GreenApple.
+ * `Apple -> myApple` tells the injector that the object referenced by
myApple will be supplied.
+
+Injection is requested whenever a new object is created. In Noop, *every*
new object is created by the injector. So to create a new String object,
you'd say `String s = String("hello")` and the injector provides the String
instance. In this case, all of the information needed to construct the
String has been provided, so the injector merely acts as a simple factory.
In another case, `BankService s = BankServiceImpl()` provides no arguments
to the constructor, so any parameters that appear in !BankServiceImpl's
constructor are requests for injection. Note that we don't support setter
injection right now, but will probably need to do it for interoperability.
+
+Sometimes a type is requested to be injected more than once in the course
of executing the program. In this case, the injector might create a new
instance, or it could inject the same instance that was used in a previous
injection. This decision is governed by a *scope*, which groups a number of
injection points together to share a single instance of the injected object.
+
+Finally, we have *parent/child injectors*. When a program begins execution
from the application entry point, Noop supplies a root injector, which is
the common ancestor of all injectors. Whenever entering a method or block
marked with the binding keyword, a new injector is created as a child of
the current injector. When a request is made for injection, the child will
supply it if possible, otherwise the injection is delegated to the parent.
This allow bindings to be managed in components, so that two subcomponents
cannot depend on each other's bindings, but can share bindings in their
shared parent component.
+
+= Builtin scopes =
+
+== Singleton ==
+
+This scope behaves as one would expect. A binding in the singleton scope
satisfies all dependents with the same instance. Singletons should be
stateless or thread-safe. This is also the default scope in Noop. If an
expression is supplied for the binding, it will be evaluated only once, at
the time it is first injected.
+
+== !ThreadLocal ==
+
+This scope satisfies any dependents which execute together on the same
thread. The semantics are similar to Java's !ThreadLocal storage context.
In practice this is viable for multithreaded worker objects, and can
provide an implementation of a Request scope for the web container scopes
(see below).
+
+== Unmanaged (no scope) ==
+
+This scope, equivalent to Spring's prototype scope, produces a new object
each time injection is requested, essentially caching nothing and managing
nothing. Unmanaged objects cannot participate in service lifecycles
(start/stop) since they are unmanaged. This is a dangerous scope, in that
it looks like a scope, is used like a scope, but behaves entirely
differently, turning a Provider into a Factory (in effect) and is mostly
useful for value objects.
+
+Note: This may not be implemented depending on the newable vs. injectable
proposal outcomes.
+
+== Web Context Scopes ==
+
+For web-tiers, Noop may have an extension module which declares Session
and Request scopes for use in J2EE containers (or which could be used in
alternate web architectures). These have proven valuable elsewhere and can
simply be implemented by backing object unique caching in the Request or
Session objects directly, as do most other containers.
+
+== About the default scope ==
+
+Dependency injection systems vary in what they consider their default
scope. Often they use "no scope" as a default, which is equivalent to
spring's _prototype_ scope. This can lead to some unclear situations,
because these aren't dependencies on a managed object, they're dependencies
on an unmanaged object, and this can lead to subtle ambiguities.
+
+Noop defaults to the Singleton scope if otherwise unindicated. This is
both the most common case, and prevents naive mistakes around passing new
copies of a type into dependents which will seem to work but subtly won't
(since they're not talking to the same instance). Such errors are caught
earlier and can be explicitly set to `unmanaged` if appropriate.
+
+= Declaring bindings =
+
+The binding operator may be used anywhere in the code to introduce a new
binding to the current injector.
+
+The binding keyword is used to group a number of bindings for reuse, and
to separate wiring from logic. It should be preferred over bindings made
inline in code.
+
+== Named binding ==
+A named binding is a block of code defining bindings. It may be defined in
a file by itself, which is similar to a Guice module, and is the preferred
way. If desired, it may also be nested within a class.
+
+{{{
+// in MyBinding.noop
+binding MyBinding {
+ This -> That;
+}
+
+// in MyClass.noop
+class MyClass {
+ binding MyScope {
+ MyInterface -> MyImplementation;
+ Port -> 9876;
+ }
+}
+}}}
+
+The binding can then be referenced by name. It can be used on a method:
+
+{{{
+String helloWorld() binding LibraryOneBindings, LibraryTwoBindings {
+ // some code
+}
+}}}
+
+or in a block:
+
+{{{
+String helloWorld() {
+ binding MyBinding {
+ // all the objects bound in MyBinding may be injected here
+ }
+}
+}}}
+
+== Anonymous inline binding declarations ==
+
+Note: this feature is somewhat troubling, as it prevents overriding by
tests, and also mixes wiring with logic. It may be dropped.
+
+When a binding is fairly simple it can be defined anonymously, just as the
named binding it is usable on the method level or a block in the method:
+
+{{{
+String helloWorld() binding (MyInterface -> MyImplementation) {
+ // some code
+}
+}}}
+
+{{{
+String helloWorld() {
+ binding (MyInterface -> MyImplementation) {
+ // all the objects created in that scope will use the bindings defined
in MyScope
+ }
+}
+}}}
+
+= Injection in tests =
+Speed is essential in unit testing, to allow the number of tests to grow
very large without reducing their usefulness as a quick feedback tool. The
root injector has different default bindings to support unit testing. When
a test is executed, types are bound to a lightweight implementation. For
example, File I/O objects like File are injected with Noop instances by
default.
=======================================
--- /PropositionForScopes.wiki Thu Sep 10 15:55:50 2009
+++ /dev/null
@@ -1,88 +0,0 @@
-#summary Proposition for scopes, which contain bindings on the stack
-#labels proposal
-
-Dependency injection being part of the language, it is important for noop
to make it easy to bind interfaces to implementations. Following is a
proposition to handle scopes cleanly by a combination of pre-defined
scopes, as well as the introduction of the {{{scope}}} keyword to solve
this problem.
-
-= Builtin scopes =
-
-== Available Scopes ==
-
-=== Singleton ===
-
-This scope behaves as one would expect. A binding in the singleton scope
satisfies all dependents with the same instance. Singletons should be
stateless or thread-safe. This is recommended as the default scope (see
below)
-
-=== ThreadLocal ===
-
-This scope satisfies any dependents within the boundaries of the same
ThreadLocal storage context. In practice this is viable for multithreaded
worker objects, and can provide an implementation of a Request scope for
the web container scopes (see below)
-
-=== Unmanaged (no scope) ===
-
-This scope, equivalent to Spring's prototype scope, produces a new object
each time injection is requested, essentially caching nothing and managing
nothing. Unmanaged objects cannot participate in service lifecycles
(start/stop) since they are unmanaged. This is a dangerous scope, in that
it looks like a scope, is used like a scope, but behaves entirely
differently, turning a Provider into a Factory (in effect) and is mostly
useful for value objects.
-
-Note: This may not be necessary depending on the newable vs. injectable
proposal outcomes.
-
-=== Web Context Scopes ===
-
-For web-tiers, noop should have an extension module which declares Session
and Request scopes for use in J2EE containers (or which could be used in
alternate web architectures). These have proven valuable elsewhere and can
simply be implemented by backing object unique caching in the Request or
Session objects directly, as do most other containers.
-
-== Default scope ==
-
-Dependency injection systems vary in what they consider their default
scope. Often they use "no scope" as a default, which is equivalent to
spring's _prototype_ scope. This can lead to some unclear situations,
because these aren't dependencies on a managed object, they're dependencies
on an unmanaged object, and this can lead to subtle ambiguities.
-
-Noop should default to the Singleton scope if otherwise unindicated. This
is both the most common case, and prevents naive mistakes around passing
new copies of a type into dependents which will seem to work but subtly
won't (since they're not talking to the same instance). Such errors are
caught earlier and can be explicitly set to {{{unmanaged}}} if appropriate.
-
-= {{{scope}}} keyword =
-
-The scope keyword can be used in multiple ways:
-
-== Named scope ==
-A named scope is a block of code defining bindings. It should be defined
in a class.
-
-{{{
-class MyClass {
-
- scope MyScope {
- MyInterface -> MyImplementation;
- MyInterface2 -> MyImplementation2;
- }
-
- String helloWorld() {
- }
-}
-}}}
-
-The scope can then be referenced by name. It can be used on a method:
-
-{{{
-String helloWorld() scope MyScope {
- // some code
-}
-}}}
-
-or creating a block in a method:
-
-{{{
-String helloWorld() {
- scope MyScope {
- // all the objects created in that scope will use the bindings defined
in MyScope
- }
-}
-}}}
-
-== Anonymous scope declarations ==
-
-When a scope is fairly simple it can be defined anonymously, just as the
named scope it is usable on the method level or a block in the method:
-
-{{{
-String helloWorld() scope { MyInterface -> MyImplementation; } {
- // some code
-}
-}}}
-
-{{{
-String helloWorld() {
- scope { MyInterface -> MyImplementation; } {
- // all the objects created in that scope will use the bindings defined
in MyScope
- }
-}
-}}}
=======================================
--- /DeveloperRoadmap.wiki Fri Nov 13 20:02:35 2009
+++ /DeveloperRoadmap.wiki Mon Nov 30 07:29:09 2009
@@ -3,10 +3,6 @@

This page is a list of things the Developers will eventually have to get
to - either tasks or language elements or tooling bits, etc. These are not
prioritized.

- _NOTE: Please don't comment on this - the developers will
ignore
- it - this is a list for us to think and work through. If
something's
- missing, please post it to the relevant e-mail lists._
-
= Unstructured list of things to get to =

== Alternate execution ==
@@ -25,7 +21,6 @@

== Development Implementation Tasks ==

-
== Build and Development Environment ==
* Eclipse/IDEA/ support
* Maven plugin
=======================================
--- /ForDevelopers.wiki Thu Nov 5 12:21:11 2009
+++ /ForDevelopers.wiki Mon Nov 30 07:29:09 2009
@@ -32,27 +32,13 @@
* Not recommended: Install JRuby (http://dist.codehaus.org/jruby/) -
1.3.1 is the current version Noop is tested with. Set your PATH variable to
include the jruby bin directory.
* Recommended: Check that you have C ruby available on your machine:
`ruby -v`. Also be sure you have the ruby development library, or you'll be
missing header files when you try to compile a gem, and get an error like
`extconf.rb:6:in 'require': no such file to load -- mkmf (LoadError)`. On
ubuntu, do this: `sudo apt-get install ruby1.8-dev`. On Mac, Header files
are not delivered by default with Mac OS X, you need to install the Xcode
Tools package after the installation. You can find it in the Optional
Installs / Xcode Tools directory on the Leopard DVD.

-Install Buildr (http://buildr.apache.org/), like this:
- * With C Ruby: `sudo gem install buildr`
- * Or, if root doesn't have JAVA_HOME set, `sudo JAVA_HOME=... gem
install buildr`
- * With JRuby: `jruby -S gem install buildr`
+Install Buildr (http://buildr.apache.org/). We are using version 1.4.0,
which is unreleased, but has much improved support for scalatest. Follow
instructions here: http://buildr.apache.org/contributing.html#edge
+which are basically
+ * `svn co http://svn.apache.org/repos/asf/buildr/trunk buildr`
+ * `cd buildr; rake setup install`
+ * If root doesn't have JAVA_HOME set, you may get an error that looks a
lot like missing Ruby headers.

Now you can run a build, which will compile and run tests: `buildr`
-
-As of 9/26/09, there's a bug in Buildr's Scalatest runner, where it
reports success even if a test fails. We submitted a patch, in the
meantime, you can monkey patch:
-
-{{{
-$ gem list --local -d buildr | grep Installed
- Installed at: /usr/lib/ruby/gems/1.8
-$ sudo vim
/usr/lib/ruby/gems/1.8/gems/buildr-1.3.4/lib/buildr/scala/tests.rb
-
-# near the end, find a block similar to this and replace with:
- while (line = input.gets) do
- failed = (line =~ /(TESTS? FAILED)|(RUN STOPPED)|(RUN
ABORTED)/) unless failed
- completed |= (line =~ /Run completed\./)
- break if (failed)
- end
-}}}

=== Maven (optional) ===
*_`NOTE: Maven build is currently broken -2009.09.28`_*
@@ -69,7 +55,15 @@

== IDE Setup ==

-You'll need to pick an IDE... some members use IDEA and some use Eclipse,
so we're agnostic. Keep in mind that Scala support varies. As of 9/9/09,
the IDEA plugin seems to work best.
+You'll need to pick an IDE... some members use IDEA and some use Eclipse,
so we're agnostic. Keep in mind that Scala support varies. As of 9/9/09,
the IDEA plugin seems to work best. Also, we have checked in the .ipr file
for IDEA, so you can open the project right away.
+
+=== IntelliJ IDEA ===
+The free community edition works fine for developing on Noop. I use
version 90.193 right now, which is the latest EAP (early access program)
release of the community edition from JetBrains.
+
+Install the Scala plugin. I had a problem with the current nightly build
not working with the latest IDEA, so try version 0.3.108 if you have
trouble.
+http://download-ln.jetbrains.com/scala/scala-intellij-bin-0.3.108.zip
+
+If you make changes to the buildfile, you can run `buildr idea` to update
the IDEA project metadata. Make sure it doesn't make extra unwanted
changes, though. I have problems with the scala facet disappearing, and the
path to the scala libraries is wrong.

=== Eclipse (generated project files) ===

@@ -81,10 +75,6 @@

If you're using maven, and you use the m2eclipse plugin, you can
simply "import as Maven project" after you generate the pom.xml in the
above maven instructions.

-=== IntelliJ IDEA ===
-
-Install the Scala plugin. For setting up a project, the built-in maven
plugin works great, but you'll have to be sure the pom.xml file is up to
date. Sync it using the instructions above.
-
= Submitting (as a committer)=

Set up your Mercurial:
=======================================
--- /ProposalForNewableVsInjectable.wiki Tue Sep 8 15:22:48 2009
+++ /ProposalForNewableVsInjectable.wiki Mon Nov 30 07:29:09 2009
@@ -5,23 +5,28 @@

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/)

-The basic idea is that a class should ideally either be constructed
entirely from things the injector knows how to provide, or entirely from
objects that another class should create at runtime. Mixing them is always
problematic, so perhaps we can disallow it in Noop.
-
-= Injector needs to provide all dependencies =
-
-Like Guice, we would never allow constructing an object partly from the
injector and partly from objects created at runtime:
-
+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 BankService(String name, DatabaseConnection c) {...}
-// Not allowed
-String foo = "bar";
-BankService b = new BankService(foo, /* some magic to get the DB connect
*/);
+class Range[Logger logger](Int start, Int end) {}
+...
+Range r = Range(1,3);
}}}

-= And what if we let the user mix'n'match? =
-
-We might actually want to be able to let the injector injects what it can
and the user
-fill in what he/she can.
+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) {
@@ -31,12 +36,9 @@
class Foo() {

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

= Even newables are mockable =
@@ -63,6 +65,8 @@
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 =

@@ -70,18 +74,8 @@

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

-= Disallow mixing newable and injectable as dependencies =
-This is controversial, because it might be really surprising. So this
would be illegal:
-{{{
-class BankService(BankDatabase db) {}
-// Can't mix newable and injectable types!
-class Email(String to, BankService service) {}
-}}}
-
-Does this work in general? Can we mark a type as newable, and then just
follow the rule that you either compose newable types or non-newable types?
-
-= A problem =
-
-We can't really tell whether an object is newable or injectable from its
compile-time type. If we are in a scope where Email is bound to an
instance, then Email is injectable within that scope, and it makes sense to
have a Foo(Email e, BankService b) constructor. But if you're outside that
scope, that Foo constructor breaks the newable/injectable rule.
-
-This may be a deal-killer for this whole idea.
+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
Reply all
Reply to author
Forward
0 new messages