You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/11/01 22:58:07 UTC
svn commit: r591153 - in /tapestry/tapestry5/trunk/tapestry-ioc/src/site:
apt/index.apt apt/overview.apt site.xml
Author: hlship
Date: Thu Nov 1 14:58:07 2007
New Revision: 591153
URL: http://svn.apache.org/viewvc?rev=591153&view=rev
Log:
Add an "overview" that explains IoC.
Added:
tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/overview.apt
Modified:
tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt
tapestry/tapestry5/trunk/tapestry-ioc/src/site/site.xml
Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt?rev=591153&r1=591152&r2=591153&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt Thu Nov 1 14:58:07 2007
@@ -146,6 +146,11 @@
See {{{http://www.pragmaticprogrammer.com/ppbook/index.shtml}The Pragmatic Programmer}} for more insights into
building solid code.
+Overview
+
+ The Tapestry IoC container takes over all the plumbing necessary for a highly scalable, extensible, thread-safe, testable
+ application. Please see the {{{overview.html}overview}} for more details.
+
Terminology
The basic unit in Tapestry IoC is a <<service>>. A service consists of a <<service interface>> and a <<service implementation>>.
Added: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/overview.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/overview.apt?rev=591153&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/overview.apt (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/overview.apt Thu Nov 1 14:58:07 2007
@@ -0,0 +1,390 @@
+ ----
+ Tapestry IoC Overview
+ ----
+
+Tapestry IoC Overview
+
+ Even today, with the overwhelming success of {{{http://www.springframework.org}Spring}} and the rise of
+ smaller, simpler approaches to building application that stand in sharp contrast to the ultra-heavyweight
+ EJB approach, many people still have trouble wrapping their heads around Inversion of Control.
+
+ Really understanding IoC is a new step for many developers. If you can remember back to when you made the transition
+ from procedural programming (in C, or BASIC) to object oriented programming, you might remember the point where you "got it". The point
+ where it made sense to have methods on objects, and data inside objects.
+
+ Inversion of Control builds upon those ideas. The goal is to make coding more robust (that is, with fewer errors), more reusable and
+ to make code much easier to test.
+
+ Most developers are used to a more <monolithic> design, they have a few core objects and a <<<main()>>> method somewhere
+ that starts the ball rolling. <<<main()>>> instantiates the first couple of classes, and those classes
+ end up instantiating and using all the other classes in the system.
+
+ That's an <unmanaged> system. Most desktop applications are unmanaged, so it's a very familiar pattern, and easy to get your head around.
+
+ Building web applications tends to be quite a different story. It's a very different environment largely because of <multithreading>.
+ Your code will be servicing many different users simultaneously across many different threads. This tends to complicate the
+ code you write, since some fundamental aspects of object oriented development get called into question: in particular, the use
+ of <internal state>, values stored inside instance variables, since in a multi-threaded environment, that's no longer the safe
+ place it is in traditional development. Shared objects plus internal state plus multiple threads equals an broken, unpredictable application.
+
+ Frameworks such as Tapestry (both the IoC container, and the web framework itself) exists to help.
+
+ When thinking in terms of IoC, <<small is beautiful>>. What does that mean? It means small classes and small methods
+ are easier to code than large ones. At one extreme, we have servlets circa 1997 (and Visual Basic before that) with methods a thousand lines long,
+ and no distinction between business logic and view logic. Everything mixed together into an untestable jumble.
+
+ At the other extreme is IoC: small objects, each with a specific purpose, collaborating with other small objects.
+
+ Using unit tests, in collaboration with tools such as {{{http://easymock.org/}EasyMock}}, you can have a code base that is easy to maintain,
+ easy to extend, and easy to test. And by factoring out a lot of <plumbing> code, your code base will not only be easier to work with, it will be smaller.
+
+Living on the Frontier
+
+ Coding applications the traditional way is like being a homesteader on the American frontier in the 1800's. You're responsible for
+ every aspect of your house: every board, every nail, every stick of furniture is something you personally created. There <is> a great
+ comfort in total self reliance, and even if house is small, the windows are a bit drafty or the floorboards creak a little, you know exactly <why>
+ things are not-quite perfect.
+
+ Flash forward to modern cities or modern suburbia and it's a whole different story. Houses are built to specification from design plans, made
+ from common materials, by many specializing tradespeople. Construction codes dictate how plumbing, wiring and framing should be performed.
+ A home-owner may not even know how to drive a nail, but can still take comfort
+ in draft-free windows, solid floors and working plumbing.
+
+ To extend the metaphor, a house in a town is not alone and self-reliant the way a frontier house is. The town house
+ is situated on a street, in a neighborhood, within a town. The town provides services
+ (utilities, police, fire control, streets and sewers) to houses in a uniform way. Each house just needs to connect up to those services.
+
+The World of the Container
+
+ So the IoC container is the "town" and in the world of the IoC container, everything has a name, a place, and a relationship
+ to everything else in the container. Tapestry calls this world "The Registry".
+
+[images/ioc-overview.png] IoC Overview
+
+ Here we're seeing a few services from the built-in Tapestry IoC module, and a few of the services from the Tapestry web framework module.
+ In fact, there are over 100 services, all interrelated, in the Registry ... and that's before you add your own to the mix. The IoC Registry
+ treats all the services uniformly, regardless of whether they are part of Tapestry, or part of your application, or part of an add-on library.
+
+ Tapestry IoC's job is to make all of these services available to each other, and to the outside world. The outside world could
+ be a standalone application, or it could be an application built on top of the Tapestry web framework.
+
+Service Lifecycle
+
+
+ Tapestry services are <lazy>, which means they are not fully instantiated until they are absolutely needed. Often, what looks like a service
+ is really a proxy object ... the first time any method of the proxy is invoked,
+ the actual service is instantiated and initialized (Tapestry uses the term <realized> for this process). Of course, this is all absolutely
+ thread-safe.
+
+ Initially a service is <defined>, meaning some module has defined the service. Later, the service will be <virtual>, meaning a proxy
+ has been created. This occurs most often because some other service <depends> on it, but hasn't gotten around to invoking methods on it. Finally, a service
+ that is ready to use is <realized>. What's nice is that your code neither knows nor cares about the lifecycle of the service, because of the magic of the proxy.
+
+ In fact, when a Tapestry web application starts up, before it services its first request, only about 20% of the services have been realized; the remainder
+ are defined or virtual.
+
+Class vs. Service
+
+ A Tapestry service is more than just a class. First of all, it is a combination of an <interface> that defines the operations of the service,
+ and an <implementation class> that implements the interface.
+
+ Why this extra division? Having a service interface is what lets Tapestry create proxies and perform other operations. It's also a very good practice to
+ code to an interface, rather than a specific implementation. You'll often be surprised at the kinds of things you can accomplish by substituting
+ one implementation for another.
+
+ Tapestry is also very aware that a service will have dependencies on other services. It may also have other needs ... for example, in Tapestry IoC, the
+ container provides services with access to Loggers.
+
+ Tapestry IoC also has support for other configuration that may be provided to services when they are realized.
+
+Dependency Injection
+
+ Inversion of Control refers to the fact that the container, here Tapestry IoC's Registry, instantiates your classes. It decides on when the classes
+ get instantiated.
+
+ Dependency Injection is a key part of <realization>: this is how a service is provided with the other services it needs to operate. For example,
+ a Data Access Object service may be injected with a ConnectionPool service.
+
+ In Tapestry, injection occurs exclusively through constructors. Other frameworks support a mix of constructor injection, property injection (i.e., invoking setter methods)
+ and method injection (invoking arbitrary methods to pass in dependencies). Tapestry focuses exclusively on constructor injection, and emphasizes
+ that dependencies should be stored in <<final>> variables. This is the best approach towards ensuring thread safety.
+
+ In any case, injection "just happens". Tapestry finds the constructor of your class and analyzes the parameters to determine what to pass in. In some cases,
+ it uses just the parameter type to find a match, in other cases, annotations on the parameters may also be used.
+
+Why can't I just use <<<new>>>?
+
+ I've had this question asked me many a time. All these new concepts seem alien. All that XML (in the Spring or HiveMind IoC containers; Tapestry IoC uses no XML) is a burden.
+ What's wrong with <<<new>>>?
+
+ The problem with new is that it rigidly connects one implementation to another implementation. Let's follow a progression that reflects how a lot of projects
+ get written. It will show that in the real world, <<<new>>> is not as simple as it first seems.
+
+ This example is built around some work I've done recently involving a Java Messaging Service queue, part of an application performance monitoring
+ subsystem for a large application. Code inside each server collects performance data of various types and sends it, via a shared JMS queue,
+ to a central server for collection and reporting.
+
+ This code is for a metric that periodically counts the number of rows in a key database table. Other implementations of MetricProducer
+ will be responsible for measuring CPU utilization, available disk space, number of requests per second, and so forth.
+
++----+
+public class TableMetricProducer implements MetricProducer
+{
+ . . .
+
+ public void execute()
+ {
+
+ int rowCount = . . .;
+ Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);
+
+ new QueueWriter().sendMetric(metric);
+ }
+}
++----+
+
+ I've elided some of the details (this code will need a database URL or a connection pool to operate),
+ so as to focus on the one method and it's relationship to the QueueWriter class.
+
+ Obviously, this code has a problem ... we're creating a new QueueWriter for each metric we write into the queue, and the QueueWriter presumably is going to
+ open the JMS queue fresh each time, an expensive operation. Thus:
+
++----+
+public class TableMetricProducer implements MetricProducer
+{
+ . . .
+
+ private QueueWriter _queueWriter = new QueueWriter();
+
+ public void execute()
+ {
+ int rowCount = . . .;
+ Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);
+
+ _queueWriter.sendMetric(metric);
+ }
++-----+
+
+ That's better. It's not perfect ... a proper system might know when the application was being shutdown and would shut down the JMS Connection
+ inside the QueueWriter as well.
+
+ Here's a more immediate problem: JMS connections are really meant to be shared, and we'll have lots of little classes collecting different metrics. So we need
+ to make the QueueWriter shareable:
+
++----+
+ private final QueueWriter _queueWriter = QueueWriter.getInstance();
++----+
+
+ ... and inside class QueueWriter:
+
++----+
+public class QueueWriter
+{
+ private static QueueWriter _instance;
+
+ private QueueWriter()
+ {
+ ...
+ }
+
+ public static getInstance()
+ {
+ if (_instance == null)
+ _instance = new QueueWriter();
+
+ return _instance;
+ }
+}
++-----+
+
+ Much better! Now all the metric producers running inside all the threads can share a single QueueWriter. Oh wait ...
+
++-----+
+ public synchronized static getInstance()
+ {
+ if (_instance == null)
+ _instance = new QueueWriter();
+
+ return _instance;
+ }
++----+
+
+ Is that necessary? Yes. Will the code work without it? Yes -- <<99.9% of the time>>. In fact, this is a very common error
+ in systems that manually code a lot of these construction patterns: forgetting to properly synchronize access. These things often work in development and testing,
+ but fail (with infuriating infrequency) in production, as it takes two or more threads running simultaneously to reveal the
+ coding error.
+
+ Wow, we're a long way from a simple <<<new>>> already, and we're talking about just one service. But let's detour into <testing>.
+
+ How would you test TableMetricProducer? One way would be to let it run and try to find the message or messages it writes
+ in the queue, but that seems fraught with difficulties. It's more of an integration test, and is certainly something
+ that you'd want to execute at some stage of your development, but not as part of a quick-running unit test suite.
+
+ Instead, let's split QueueWriter in two: a QueueWriter interface, and a QueueWriterImpl implementation class. This will allow
+ us to run TableMetricProducer against a <mock implementation> of QueueWriter, rather than the real thing. This is one
+ of the immediate benefits of <coding to an interface> rather than <coding to an implementation>.
+
+ We'll need to change
+ TableMetricProducer to take the QueueWriter as a constructor parameter.
+
++----+
+public class TableMetricProducer implements MetricProducer
+{
+ private final QueueWriter _queueWriter;
+
+ /**
+ * The normal constructor.
+ *
+ */
+ public TableMetricProducer(. . .)
+ {
+ this(QueueWriterImpl.getInstance(), . . .);
+ }
+
+ /**
+ * Constructor used for testing.
+ *
+ */
+ TableMetricProducer(QueueWriter queueWriter, . . .)
+ {
+ _queueWriter = queueWriter;
+ . . .
+ }
+
+ public void execute()
+ {
+ int rowCount = . . .;
+ Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);
+
+ _queueWriter.sendMetric(metric);
+ }
+}
++----+
+
+ This still isn't ideal, as we still have an explicit linkage between TableMetricProducer and QueueWriterImpl.
+
+ What we're seeing here is that there are multple <concerns> inside the little bit of code in this example. TableMetricProducer has an unwanted
+ <construction concern> about which implementation of QueueWriter to instantiate (this shows up as two constructors,
+ rather than just one). QueueWriterImpl has an additional <lifecycle concern>, in terms
+ of managing the singleton.
+
+ These extra concerns, combined with the use of static variables and methods, are a <bad design smell>. It's not yet very stinky, because
+ this example is so small, but these problems tend to multiply as an application grows larger and more complex, especially as services
+ start to truly collaborate in earnest.
+
+ For comparison, lets see what the Tapestry IoC implementation would look like:
+
++----+
+public class MonitorModule
+{
+ public static void bind(ServiceBinder binder)
+ {
+ binder.bind(QueueWriter.class, QueueWriterImpl.class);
+ binder.bind(MetricScheduler.class, MetricSchedulerImpl.class);
+ }
+
+ public void contributeMetricScheduler(Configuration<MetricProducer> configuration, QueueWriter queueWriter, . . .)
+ {
+ configuration.add(new TableMetricProducer(queueWriter, . . .))
+ }
+}
++----+
+
+ Again, I've elided out a few details related to the database the TableMetricProducer will point at (in fact, Tapestry IoC
+ provides a lot of support for configuration of this type as well, which is yet another concern).
+
+ The MonitorModule class is a Tapestry IoC module: a class that defines and configures services.
+
+ The bind() method is the principle way that services are made known to the Registry: here we're binding
+ a service interface to a service implementation. QueueWriter we've discussed already,
+ and MetricScheduler is a service that is responsible for determining when MetricProducer instances
+ run.
+
+ The contributeMetricScheduler() method allows the module to <contribute> into the MetricProducer service's <configuration>. More testability:
+ the MetricProducer isn't tied to a pre-set list of producers, instead it will have a Collection<MetricProducer> injected into its
+ constructor. Thus, when we're coding the MetricProducerImpl class, we can test it against mock implementations of MetricProducer.
+
+ The QueueWriter service in injected into the contributeMetricScheduler() method. Since there's only one QueueWriter service,
+ Tapestry IoC is able to "find" the correct service based entirely on type. If, eventually, there's more than one QueueWriter service
+ (perhaps pointing at different JMS queues), you would use an annotation on the parameter to help Tapestry connect the parameter to the appropriate service.
+
+ Presumably, there'd be a couple of other parameters to the contributeMetricScheduler() method, to inject in a database URL or connection pool
+ (that would, in turn, be passed to TableMetricProducer).
+
+ A new TableMetricProducer instance is created and contributed in. We could contribute as many producers as we like here. Other modules could also
+ define a contributeMetricScheduler() method and contribute their own MetricProducer instances.
+
+ Meanwhile, the QueueWriterImpl class no longer needs the _instance variable or getInstance() method, and the TableMetricProducer
+ only needs a single constructor.
+
+Advantages of IoC: Summary
+
+ It would be ludicrous for us to claim that applications built without an IoC container are doomed to failure. There is overwhelming evidence
+ that applications have been built without containers and have been perfectly successful.
+
+ What we are saying is that IoC techniques and discipline will lead to applications that are:
+
+ * More testable -- smaller, simpler classes; coding to interfaces allows use of mock implementations
+
+ * More robust -- smaller, simpler classes; use of final variables; thread safety baked in
+
+ * More scalable -- thread safety baked in
+
+ * Easier to maintain -- less code, simpler classes
+
+ * Easier to extend -- new features are often additions (new services, new contributions) rather than changes to existing classes
+
+ []
+
+ What we're saying is that an IoC container allows you to work faster and smarter.
+
+
+ Many of these traits work together; for example, a more testable application is inherently more robust. Having a test suite
+ makes it easier to maintain and extend your code, because its much easier to see if new features break existing ones.
+ Simpler code plus tests also lowers the cost of entry for new developers
+ coming on board, which allows for more developers to work efficiently on the same code base. The clean separation between
+ interface and implementation also allows multiple developers to work on different aspects of the same code
+ base with a lowered risk of interference and conflict.
+
+ By contrast, traditional applications, which we term <monolithic> applications, are often very difficult to test, because
+ there are fewer classes, and each class has multiple concerns. A lack of tests makes it more difficult to
+ add new features without breaking existing features. Further, the monolithic approach
+ more often leads to implementations being linked to other implementations, yet another hurdle standing in the way of testing.
+
+ Let's end with a metaphor.
+
+ Over a decade ago, when Java first came on the scene, it was the first mainstream language to support garbage collection.
+ This was very controversial: the garbage collector was seen as unnecessary, and a waste of resources. Among
+ C and C++ developers, the attitude was "Why do I need a garbage collector? If I call malloc() I can call free()."
+
+ I don't know about you, but I don't think I could ever go back to a non-garbage collected environment. Having the GC
+ around makes it much easier to code in a way I find natural: many small related objects working together. It turns out
+ that knowing when to call free() is more difficult than it sounds. The Objective-C language tried to solve this with retain
+ counts on objects and that still lead to memory leaks when it was applied to object <graphs> rather than object <trees>.
+
+ Roll the clock forward a decade and the common consensus has shifted considerably. Objective-C 2.0 features
+ true garbage collection and GC libraries are available for C and C++. All scripting languages, including Ruby and Python, feature
+ garbage collection as well. A new language <without> garbage collection is now considered an anomaly.
+
+ The point is, the lifecycle of objects turns out to be far more complicated than it looks at first glance. We've come to accept that our
+ own applications lack the ability to police their objects as they are no longer needed (they literally lack the ability to determine
+ <when> an object is no longer needed) and the garbage collector, a kind of higher authority, takes over that job very effetively. The end result?
+ Less code and fewer bugs. And a careful study shows that the Java memory allocator and garbage collector (the two are
+ quite intimately tied together) is actually
+ <<more>> efficient that malloc() and free().
+
+ So we've come to accept that the <death concern> is better handled outside of our own code. The use of Inversion of Control
+ is simply the flip side of that: the <lifecycle and construction concerns> are also better handled by an outside authority as well: the IoC container.
+ These concerns govern when a service is <realized> and how its dependencies and configuration are injected. As with
+ the garbage collector, ceding these chores to the container
+ results in less code and fewer bugs, and lets you concentrate on the things that should matter to you: your business logic, your application -- and not
+ a whole bunch of boilerplate plumbing!
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/site.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/site.xml?rev=591153&r1=591152&r2=591153&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/site.xml (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/site.xml Thu Nov 1 14:58:07 2007
@@ -46,7 +46,8 @@
</menu>
<menu name="Tapestry IoC Container">
- <item name="Introduction" href="index.html"/>
+ <item name="Introduction" href="index.html"/>
+ <item name="Overview of Tapestry IoC" href="overview.html"/>
<item name="Modules" href="module.html"/>
<item name="Services" href="service.html"/>
<item name="Decorators" href="decorator.html"/>