Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!
Showing posts with label ioc. Show all posts
Showing posts with label ioc. Show all posts

Thursday, May 23, 2013

Once more ... feedback please!

You've probably heard about "Not Invented Here" syndrome: the drive among developers to create something of their own, rather than just use an off-the-shelf library or component. It's almost universally painted as a bad thing, a sign of immaturity, or even arrogance.

But there's a flip side to this: every bit of code ever written contains within it tradeoffs: speed versus maintainability is a common tradeoff that everyone has seen. Perhaps the code is insufficiently flexible in the face of real-world requirements, but is really well tested for what it does cover. These choices reflect the developer's principles applied to the code. In fact, it is rare for it to be an easy give-and-take between two simple goals; more likely, there's lots of conflicting goals in the code, in the requirements, and in the developer's head. "Not Invented Here" can also mean "Not Reflecting My Principles".

Tapestry has it own set of guiding principals: Simplicity, Consistency, Efficiency, and Feedback ... and as a reusable framework, Feedback is very important. Feedback may be the most important principle when things go wrong. A framework that obscures problems, through bad feedback, is a framework that shouldn't be used.

Which brings us back to "Not Invented Here". Only in an impossibly perfect world would there be some ideal blob of code out there, ready to be reused, with zero impedance mismatch issues. In fact, when you bring in other people's code, you are forced to mesh your goals and principals with theirs. In my case, I'm adding support to Tapestry for converting Less files to CSS, using WRO4J (Web Resource Optimizer for Java). They've been working on WRO4J for several years, it makes sense to reuse their code, and it would be arrogant to think I could whip something better together under any kind of time constraints.

In fact, it's actually been pretty smooth sailing ... until we tripped across a violation of Tapestry's Feedback principle. As soon as I tested an error case, where the Less source file was not valid I hit bad feedback. Can you spot what's wrong with this exception report?

Oops! Looks like someone didn't get the memo about the importance of toString(). See, that Feedback principal is important to me, but for the majority of developers, useful feedback is too often an afterthought. I don't want to single out WRO4J here ... I'm pretty disdainful about feedback in nearly all software: open source or proprietary.

So what are our options here?

  • Write our own wrappers around the Less Processor, and throw out WRO4J
  • Beg the WRO4J guys to implement a real toString(), and wait for the next release
  • Fork WRO4J in the short term, and hope they'll take a patch in the long term
  • Patch around this reporting problem

Obviously, we should find a way to patch the reporting problem; we don't want to throw out the baby with the bath water. Fortunately, Tapestry provides the necessary hooks to override how it presents objects inside the exception report; it's all about providing a mapping from a Java type to a matching implementation of ObjectRenderer. Because of Tapestry's IoC container, this is actually quite straight forward:

And with those changes, the exception is presented quite differently:

Well, those are actually the raw ANTLR parser errors, but at least that's enough to help you find location of the problem ... whereas, with the bad feedback, you would only know that there was an issue somewhere in your Less source file.

Monday, February 27, 2012

Plastic: Advanced Example

Plastic is Tapestry's built-in Aspect Oriented Programming library, which primarily operates at the byte code level, but shields you from most byte code level thinking: normally, your code is implemented in terms of having method invocations or field reads and writes passed to callback objects that act as delegates or filters.

Sometimes, though, you need to get a little more low-level and generate the implementation of a method more directly. Plastic includes a fluent interface for this as well: InstructionBuilder.

This is an example from Tapestry's Inversion Of Control (IoC) container code; the proxy instance is the what's exposed to other services, and encapsulates two particular concerns: First, the late instantiation of the actual service implementation, and second, the ability to serialize the proxy object (even though the services and other objects are decidedly not serializable).

In terms of serialization, what actually gets serialized is a ServiceProxyToken object; when a ServiceProxyToken is later de-serialized, it can refer back to the equivalent proxy object in the new JVM and IoC Service Registry. The trick is to use the magic writeReplace() method so that when the proxy is serialized, the token is written instead. Here's the code:

To kick things off, we use the PlasticProxyFactory service to create a proxy that implements the service's interface.

The callback passed to createProxy() is passed the PlasticClass object. This is initially an implementation of the service interface where each interface method does nothing.

The basic setup includes making the proxy implement Serializable and creating and injecting values into new fields for the other data that's needed.

Next, a method called delegate() is created; it is responsible for lazily creating the real service when first needed. This is actually encapsulated inside an instance of ObjectCreator; the delegate() method simply invokes the create() method and casts the result to the service interface.

The methods on InstructionBuilder have a very close correspondence to JVM byte codes. So, for example, loading an instance field involves ensuring that the object containing the field is on the stack (via loadThis()), then consuming the this value and replacing it with the instance field value on the stack, which requires knowing the class name, field name, and field type of the field to be loaded. Fortunately, the PlasticField knows all this information, which streamlines the code.

Once the ObjectCreator is on the stack, a method on it can be invoked; at the byte code level, this requires the class name for the class containing the method, the return type of the method, and the name of the method (and, for methods with parameters, the parameter types). The result of that is the service implementation instance, which is cast to the service interface type and returned.

Now that the delegate() method is in place, it's time to make each method invocation on the proxy invoke delegate() and then re-invoke the method on the late-instantiated service implementation. Because this kind of delegation is so common, its supported by the delegateTo() method.

introduceMethod() can access an existing method or create a new one; for the writeReplace() method, the introduceMethod call creates a new, empty method. The call to changeImplementation() is used to replace the default empty method implementation with a new once; again, loading an injected field value, but then simply returning it.

Finally, because I feel strongly about including a useful toString() method in virtually all objects, this is also made easy in Plastic.

Once the class has been defined, it's just a matter of invoking the newInstance() method on the ClassInstantiator object to instantiate a new instance of the proxy class. Behind the scenes, Plastic has created a constructor to set the injected field values, but another of the nice parts of the Plastic API is that you don't have to manage that: ClassInstantiator does the work.

I'm pretty proud of the Plastic APIs in general; I think they strike a good balance between making common operations simple and concise, but still providing you with an escape-valve to more powerful (or more efficient) mechanisms, such as the InstructionBuilder examples above. Of course, the deeply-nested callback approach can be initially daunting, but that's mostly a matter of syntax, which may be addressed in JDK 8 with the addition of proper closures to the Java language.

I strongly feel that Plastic is a general purpose tool, that goes beyond inversion of control and the other manipulations that are specific to Tapestry ... and Plastic was designed specifically to be reused outside of Tapestry. It seems like it could be used for anything from implementing simple languages and DSLs, to providing all kinds of middleware code in new domains ... I have a fuzzy idea involving JMS and JSON with a lot of wiring and dispatch that could be handled using Plastic. I'd love to hear other people's ideas!

Thursday, February 11, 2010

Live reloading of Tapestry services?

During today's Tapestry Training at SkillsMatter, the question about live class reloading for Tapestry services came up.

Now, my normal response is to talk about class loaders, and mysterious class-cast exceptions it would cause, and the need to shut down and restart the container, etc.

But an idea went around ... what about just live reloading of implementation classes? That sparked some thoughts.

See, it seems to me that it should be possible to create a class loader that loads a single class (and, perhaps, inner classes of that single class), much as Tapestry uses a class loader to load pages and components.

In fact, it should be possible to have separate class loader for every implementation class that just performs the reload of that one class. A periodic check of the file modification date stamps could trigger the release of the class loader (and the current instance) and the instantiation of a new class loader, and the loading of the updated class.

You wouldn't be able to change service interfaces this way, or module classes (including contributions and the like) ... but changing a service implementation should be a snap. This would especially be useful for DAOs while creating and tuning database queries.

I think there would be some limitations here: services that are built via builder methods would not work; neither would services that export this (typically, as a listener to events published by another service). However, the vast majority of services could, I think, be automatically reloaded.

This is worth spending some time on ... if I can pull it off, it would be an incredible coup!

The only downside is that some services may need to move from tapestry-core to tapestry-ioc and some of those may, in fact, be public already (but not widely used).

Thursday, September 11, 2008

Tapestry 5 IoC: Introducing Service Configurations

In the previous article, I discussed the basics of Tapestry 5 IoC. I focused on the terseness of Tapestry's container, even though everything occurs in Java code. I alluded to special features of Tapestry 5 IoC, service configurations. Let's start investigating those.

In traditional dependency injection, the relationship between a service and its dependencies is many-to-one: many services may inject a specific dependency. Whether that dependency is selected just by service type, or by service id, or by some other mechanism, it's still one single object.

Service configurations are somewhat inverted: they are a relationship from one service to many objects. The objects, or contributions, may be simple objects or may themselves be services.

Let's use a specific example from Tapestry to put this into perspective. Previously I showed the service builder method for the TranslatorSource service:

public static TranslatorSource buildTranslatorSource(ComponentInstantiatorSource componentInstantiatorSource, 
  ServiceResources resources)
{
    TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class);

    componentInstantiatorSource.addInvalidationListener(service);

    return service;
}

Let's dive a little deeper and look at what this service does. It's a source for Translator objects, which are an integral part of Tapestry's HTML form support. Translators are responsible for converting between server-side values (such as numbers, dates, and so forth) and client-side strings. They also play a role in client-side validation of user input.

Tapestry matches up properties that are edited by TextFields with corresponding Translator instances. This all happens inside the TextField component and is largely invisible to programmers. In any case, the TranslatorSource service is central:

public interface TranslatorSource
{
    /**
     * Returns the translator with the given logical name.
     *
     * @param name name of translator (as configured)
     * @return the shared translator instance
     * @throws RuntimeException if no translator is configured for the provided name
     */
    Translator get(String name);

    /**
     * Finds a {@link Translator} that is appropriate to the given type, which is usually obtained via {@link
     * org.apache.tapestry5.Binding#getBindingType()}. Performs an inheritanced-based search for the best match.
     *
     * @param valueType the type of value for which a default translator is needed
     * @return the matching translator, or null if no match can be found
     */
    Translator findByType(Class valueType);

    /**
     * Finds a {@link Translator} that is appropriate to the given type, which is usually obtained via {@link
     * org.apache.tapestry5.Binding#getBindingType()}. Performs an inheritanced-based search for the best match.
     *
     * @param valueType the type of value for which a default translator is needed
     * @return the matching translator
     * @throws IllegalArgumentException if no known validator matches the provided type
     */
    Translator getByType(Class valueType);
}

Here's where it gets interesting

So, what Translators are built into Tapestry? You might think you could tell by looking at the implementation of the service:

public class TranslatorSourceImpl implements TranslatorSource, InvalidationListener
{
    private final Map<String, Translator> translators = CollectionFactory.newCaseInsensitiveMap();

    private final StrategyRegistry<Translator> registry;

    public TranslatorSourceImpl(Collection<Translator> configuration)
    {
        Map<Class, Translator> typeToTranslator = CollectionFactory.newMap();

        for (Translator t : configuration)
        {
            translators.put(t.getName(), t);
            typeToTranslator.put(t.getType(), t);
        }

        registry = StrategyRegistry.newInstance(Translator.class, typeToTranslator, true);
    }

    public Translator get(String name)
    {

        Translator result = translators.get(name);

        if (result == null)
            throw new RuntimeException(ServicesMessages.unknownTranslatorType(name, InternalUtils
                    .sortedKeys(translators)));

        return result;
    }

    public Translator getByType(Class valueType)
    {
        Translator result = registry.get(valueType);

        if (result == null)
        {
            List<String> names = CollectionFactory.newList();

            for (Class type : registry.getTypes())
            {
                names.add(type.getName());
            }

            throw new IllegalArgumentException(ServicesMessages.noTranslatorForType(valueType, names));
        }

        return result;
    }

    public Translator findByType(Class valueType)
    {
        return registry.get(valueType);
    }

    public void objectWasInvalidated()
    {
        registry.clearCache();
    }
}

But you don't see any pre-defined Translator instances here ... just Collection<Translator> configuration passed to the constructor. Each Translator provides its name, and those all go into the translators map ... but the question is, where do they come from?

Jumping back to TapestryModule, we see a likely method:

public static void contributeTranslatorSource(Configuration<Translator> configuration)
{
    configuration.add(new StringTranslator());
    configuration.add(new ByteTranslator());
    configuration.add(new IntegerTranslator());
    configuration.add(new LongTranslator());
    configuration.add(new FloatTranslator());
    configuration.add(new DoubleTranslator());
    configuration.add(new ShortTranslator());
}

It's looking pretty likely that Tapestry supports string, byte, integer, long, float, double and short out of the box. The naming of this method is another example of convention over configuration. The prefix this time is contribute and the rest of the method name matches the service id, TranslatorSource.

The Configuration object has a single method, add():

public interface Configuration<T>
{
    /**
     * Adds an object to the service's contribution.
     *
     * @param object to add to the service's configuration
     */
    void add(T object);
}

So, Tapestry has invoked the contributeTranslatorSource() method, collected up the objects, the Translators, added to the configuration object, and converted the configuration object to a Collection, which is ultimately passed to the TranslatorSourceImpl constructor.

Seems awfully complicated, doesn't it? Well, it is nice (from a testing perspective) that TranslatorSourceImpl isn't tied to any particular implementations of Translator. But that's not the real benefit.

The real benefit, and this is the basis of the entire concept, is that you are not locked into just these Translators. You can define your own, and mix them in with the ones supplied by Tapestry. And you don't have to hack TapestryModule or TranslatorSourceImpl to do it.

Say your application defines a Currency class, to track currency amounts of orders and payments. That might be handy to use instead of double, for accuracy reasons. You might also want to parse and format currency values differently than naked doubles ... for example, to require exactly two digits of precision, or to ignore a leading dollar sign.

To mix in your own Translators, all you need to do is define your own module:

public class AppModule
{
  public static void contributeTranslatorSource(Configuration<Translator> configuration)
  {
    configuration.add(new CurrencyTranslator());
  }
}

This translator, CurrencyTranslator, will be indistinguishable from the default set of Translators provided by Tapestry; TranslatorSourceImpl will have no way of telling which Translators came from where. Your contributions are on an even footing with those provided by Tapestry itself.

You might ask in what order are these contribute methods are invoked? The answer is: Who knows? That's why the configuration is passed to the service implementation as (unordered) Collection, not (ordered) List. As we'll see in the next article, Tapestry has alternatives for when you care about ordering, or when you want your configuration in the form of a Map.

When are these methods called? They are called when the TranslatorSource service is realized, which happens when a method of the service is first invoked. Tapestry IoC is by default very lazy, it doesn't instantiate services until necessary. The service's proxy is responsible for this realization process, and its done in a thread-safe manner. Dealing with this kind of loose binding in a structured manner is very helpful: it means that the TranslatorSource service is simplified: it doesn't need a method to add a new Translator, there's fewer issues related to multiple threads, and the available set of Translators never changes, which makes the behavior of the service much more predictable.

Conclusion

We've only just pierced the surface of Tapestry configurations, but we're beginning to see what I mean when talk about Tapestry's extensibility. Much of the key behavior of Tapestry is specified in terms of this kind of configuration, or one of its close relatives. And, as the example showed, building a service that uses a configuration is just a matter of defining a parameter of type Collection in the service's constructor.

In future articles, I'll discuss other variations of service configurations, and show how to go meta with Tapestry by leveraging configurations in combination with service-building services!

Tuesday, August 26, 2008

Tapestry 5 IoC: Binding and Building Services

Tapestry 5 includes its own internal Inversion of Control container. This is often a point of contention ... why not just use Spring or (in more recent conversations) Guice?

That's a complex question; simply put, Tapestry has requirements as a framework that the other containers don't offer solutions to.

This posting is a simple introduction to the basics of Tapestry 5 IoC. In later postings, we'll get into more detail about the advanced features of Tapestry's IoC container, the ones that really distance it from Spring and Guice.

Tapestry uses the term "service" for the primary objects that it manages for you. Spring uses the term "bean". A service is normally an interface and a class that implements the interface. In the most typical case, only a single service implements the interface, but T5 IoC is fully capable of handling the case where one service interface has a number of distinct services; even the case where a single class is instantiated with a different configuration.

Every service has a unique id string. In most cases, this is just the simple name of the service interface. When the same interface is used by multiple services, you will have to identify the service id explicitly.

To keep things real, I'll use actual, though abbreviated, examples from Tapestry's code base.

T5 IoC uses module classes to identify what services are available. A module class is a POJO class with a special method on it, a method named bind(). A Tapestry application will consist of a number of modules: some modules provided by Tapestry itself, some by third party libraries or extensions, and some by the application itself. Tapestry mixes and matches all of this information, all of the services defined by each of the modules, into a single registry of services.

That may sound more complex than it really is. The reality is that in the bind() method, we simply match service interfaces to corresponding implementations:

public final class TapestryModule
{
    public static void bind(ServiceBinder binder)
    {
        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
        binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
        binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
        // ... and so on
    }
}

The ServiceBinder is uses generics to ensure that the class you specify implements the service interface. The API is a fluent interface: you can chain a few extra method calls onto bind() to override defaults, for example:

      binder.bind(ObjectProvider.class, AssetObjectProvider.class).withId("AssetObjectProvider");

TapestryModule actually defines quite a few additional services.

Let's look at an example:

public interface PersistentLocale
{
    void set(Locale locale);

    Locale get();

    boolean isSet();
}

I've stripped out the comments to save space ... but this service manages the user's locale; it's a key part of Tapestry 5's localization support. The implementation we'll see shortly works using HTTP Cookies, but that isn't important to the code that uses PersistentLocale.

public class PersistentLocaleImpl implements PersistentLocale
{
    private static final String LOCALE_COOKIE_NAME = "org.apache.tapestry5.locale";

    private final Cookies cookieSource;

    public PersistentLocaleImpl(Cookies cookieSource)
    {
        this.cookieSource = cookieSource;
    }

    public void set(Locale locale)
    {
        cookieSource.writeCookieValue(LOCALE_COOKIE_NAME, locale.toString());
    }

    public Locale get()
    {
        String localeCookieValue = getCookieValue();

        return localeCookieValue != null ? LocaleUtils.toLocale(localeCookieValue) : null;
    }

    private String getCookieValue()
    {
        return cookieSource.readCookieValue(LOCALE_COOKIE_NAME);
    }

    public boolean isSet()
    {
        return getCookieValue() != null;
    }
}

T5 IoC does all injection through the constructor. This is to encourage you to write your dependencies into final fields, which is thread safe. Typically, your services will be immutable objects: all fields final.

PersistentLocaleImpl has a dependency on another service, Cookies. And what is Cookies? It's another service interface. Notice that we don't have to do any extra configuration here ... since there's one, and only one, service that implements the Cookies interface, that's all the information Tapestry needs to wire things together.

Other service implementations inside Tapestry have as few as zero dependencies, and as many as eight. There's no theoretical limit, it's just that having more than a few dependencies is a design smell ... that you can break things into smaller pieces.

One of the hallmarks of coding using an IoC container is this level of terseness, also knows as passing the buck. Given that PersistentLocaleImpl is concerned with HTTP cookies, you'd think that it would, somehow, get ahold of the HttpServletRequest object and start invoking getCookies() and addCookie() on it ... but instead, all the details of interfacing with the Servlet API and the rather awkward API for HTTP cookies is swept into a corner, inside the Cookies service implementation.

That's great ... it makes the implementation of PersistentLocaleImpl (as well as any other code that happens to care about HTTP cookies) that much simpler and easier to test.

Service Lifecycle

Tapestry services have a specific lifecycle:

defined
Identified via the ServiceBinder, but not yet referenced
virtual
A proxy exists that has been injected as a dependency of some other service, but no methods of the proxy have been invoked
realized
The service has been instantiated with dependencies

The beauty of this is that your code is completely unaware of this; all the work inside Tapestry ... creating proxies, realizing service implementations, occurs in a lazy but thread-safe manner. It's as if all the services are instantiated at startup without taking the time to actually do that work.

Again, the appeal of an IoC container is that you get to break your application into tiny, easily tested bits, and the IoC container is responsible for connecting everything back together at runtime. It really leads to a new way of coding, and thinking about coding.

Service Builder Methods

Sometimes just instantiating a class is not enough; there may be additional configuration needed as part of instantiating the class. Tapestry 5 IoC's predecessor, HiveMind, accomplished such goals with complex service-building services. It ended up being a lot of XML.

T5 IoC accomplishes the same, and more, using service builder methods; module methods that construct a service. A typical case is when a service implementation needs to listen to events from some other service:

public static TranslatorSource buildTranslatorSource(ComponentInstantiatorSource componentInstantiatorSource, 
  ServiceResources resources)
{
    TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class);

    componentInstantiatorSource.addInvalidationListener(service);

    return service;
}

Module methods prefixed with "build" are service builder methods. The service interface is defined from the return value (TranslatorSource). The service id is explicitly "TranslatorSource" (that is, everything after "build" in the method name).

Here, Tapestry has injected into the service builder method. ComponentInstantiatorSource is a service that fires events. ServiceResources is something else: it is a bundle of resources related to the service being constructed ... including the ability to instantiate an object including dependencies. What's great here is that buildTranslatorSource() doesn't need to know what the dependencies of TranslatorSourceImpl are, it can instantiate the class with dependencies using the autobuild() method. The service builder then adds the new service as a listener of the ComponentInstantiatorSource, before returning it.

This is a great separation of concerns: we have a construction concern (being an event listener) that's distinct from the operational concerns of TranslatorSource. And they are kept separate.

Conclusion

Tapestry IoC has simple and concise API for defining services and, in most cases, handles dependencies automatically. The end result is that it becomes child's play to divide-and-conquer: convert old, monolithic, hard to maintain code into small, easily tested, easily understood services.

In future postings, I'll go into more detail about the more advanced features of Tapestry: service scopes, service configurations and service decorations.

Monday, June 09, 2008

Using Groovy for IoC Service Decorators

Continuing with my experiments with Groovy, I took a pass at writing a service decorator in Groovy. In Tapestry, the RequestHandler service is the primary execution path; everything filters through it. It's an extensible service, based on the pipeline pattern, so I could contribute a filter into the service, but instead I wrote a decorator around the service.

    def decorateRequestHandler(service, Logger logger)
    {
        {request, response ->
            def start = System.nanoTime()

            def result = service.service(request, response)

            def elapsed = System.nanoTime() - start

            logger.info(String.format("Request time: %5.3f s -- %s", elapsed * 10E-9d, request.path))

            return result
        } as RequestHandler
    }

Tapestry's naming convention is that a module class method named decorateXXX is a decorator for service XXX. The service implementation is passed in. The return value must be the same type as the service (that is, implement the service's interface). Again, we use a closure in Groovy as an easy alternative to an inner class in Java. The code here logs the elapsed time (in seconds) for the request.

What's interesting is just how much gets into the stack trace:

 at org.apache.tapestry5.internal.services.CheckForUpdatesFilter.service(CheckForUpdatesFilter.java:106)
 at $RequestHandler_11a6ead473a.service($RequestHandler_11a6ead473a.java)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:585)
 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
 at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:230)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:912)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:756)
 at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:766)
 at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:754)
 at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:170)
 at com.nfjs.hls.blog.services.AppModule$_decorateRequestHandler_closure2.doCall(AppModule.groovy:49)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:585)
 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
 at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:230)
 at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:248)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:756)
 at groovy.lang.Closure.call(Closure.java:292)
 at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:48)
 at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:72)
 at $Proxy147.service(Unknown Source)
 at $RequestHandler_11a6ead4732.service($RequestHandler_11a6ead4732.java)

In this view, $RequestHandler_11a6ead4732 is a Tapestry runtime fabricated class, the proxy for the service. $Proxy147 is a JDK dynamic proxy which is eventually hooked up to the closure code. The service parameter (to the decorateRequestHandler) is the instance of CheckForUpdatesFilter.

What looks like a simple method invocation turns into several levels of indirection through the Closure and MetaClass code, and then ultimately a reflective method invocation before it picks back up with the first filter in the pipeline (CheckForUpdatesFilter).

This is not especially troubling; I suspect it would be all but impossible to measure the "extra" time the Groovy code takes (vs. an equivalent Java implementation). Still, this could easily add up inside a tight loop, such as the rendering state machine built into Tapestry components. Certainly Merlyn was having performance issues with his visual processing application.

Thursday, April 17, 2008

Service Dependencies in Tapestry 5

I was curious to see if I could generate a diagram that showed how all the services inside Tapestry 5 are interconnected. In the diagram, solid lines are dependencies, dashed lines are contributions, and dotted lines represent a services that listens to events from another service.

The final diagram is a bit complex. Ok, that's a tremendous understatement. Yellow nodes are public services, grey nodes are internal services, and orange nodes are simple beans (contributed into services) ... those beans, though, may still have their own dependencies. I think OmniGraffle choked on the complexity (why do some of the lines zip around so aimlessly?) and kind of "phoned it in" in terms of layout. I had hoped for something that could go on a T-shirt.

Worse yet, this is only a partial diagram; I didn't cover many of the dependencies that arrive as contributions to services (there's just a few of those in there). Those contributions would show up as even more orange nodes; these additional nodes would provide connections to many of the outliers that appear to be unreferenced.

Is this good or bad? Is it too complex? Can it be simplified? Does all that really need to be in there?

I think it is good: When I'm coding, I find it easy enough to work from a service interface, to the implementation of the interface, to the dependencies of the implementation. This works for me when developing Tapestry itself, or writing apps in Tapestry. Since there are so many small objects, no single object is large, or complex, or hard to understand or debug. It's a bit daunting to know where to start, I'm sure. However, only a few of these services are all that relevant for day-to-day development of Tapestry.

The moral of the story is that simplicity is hard! In Tapestry, pages and templates are as simple and straight forward as I think is possible. There's no excess, no distractions, no clutter, no boilerplate. Much of the logic represented in the diagram is to "fill around the edges" of this simplicity, to bridge the gap from the simple Tapestry component model to the much more complex world of stateless HTTP and multi-threaded request handling.

Tuesday, April 24, 2007

Pleasing the Crowds, Improving IoC, Extending the Community

I've been quite busy of late: a nearby Tapestry 4 project to pay the bills, a bunch of chances to talk to crowds about Tapestry 5 (in Philadelphia, Minneapolis and at home in Portland, Oregon).

I've significantly changed my Tapestry 4 presentation; it now highlights the BeanForm component, then the Table component and lastly Trails. I'm showing the high end of what's possible in Tapestry, rather than showing people the gory little details up front. That usually gets their attention.

I then show what's going on in Tapestry 5 and that really get's peoples jaws dropping. People crave this. They want to use it today. That is the desired effect. Case-insensitive, pretty URLs. Live class reloading. No XML. Incredible performance. Best of breed exception reporting. And we've barely gotten started yet.

I've been busy: Guice has set a high bar for IoC containers, but it still doesn't have certain features that Tapestry is dependent upon. That hasn't slowed me from taking the best ideas from it. Tapestry 5 now has a similar approach to autobuilding, and in most cases, a Tapestry service no longer needs a service builder method. Instead, a module can define a static bind() method and use the ServiceBinder to tell Tapestry about service interfaces and service implementations: Here's an example from the Tapestry module itself:

    public static void bind(ServiceBinder binder)
    {
        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
        binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
        binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
        binder.bind(
                ApplicationStatePersistenceStrategySource.class,
                ApplicationStatePersistenceStrategySourceImpl.class);
        binder.bind(BindingSource.class, BindingSourceImpl.class);
        binder.bind(TranslatorSource.class, TranslatorSourceImpl.class);
        binder.bind(PersistentFieldManager.class, PersistentFieldManagerImpl.class);
        binder.bind(FieldValidatorSource.class, FieldValidatorSourceImpl.class);
        binder.bind(ApplicationGlobals.class, ApplicationGlobalsImpl.class);
        binder.bind(AssetSource.class, AssetSourceImpl.class);
        binder.bind(Cookies.class, CookiesImpl.class);
        binder.bind(Environment.class, EnvironmentImpl.class);
        binder.bind(FieldValidatorDefaultSource.class, FieldValidatorDefaultSourceImpl.class);
        binder.bind(RequestGlobals.class, RequestGlobalsImpl.class);
        binder.bind(ResourceDigestGenerator.class, ResourceDigestGeneratorImpl.class);
        binder.bind(ValidationConstraintGenerator.class, ValidationConstraintGeneratorImpl.class);
        binder.bind(EnvironmentalShadowBuilder.class, EnvironmentalShadowBuilderImpl.class);
        binder.bind(ComponentSource.class, ComponentSourceImpl.class);
        binder.bind(BeanModelSource.class, BeanModelSourceImpl.class);
    }

The ServiceBinder interface is fluent, we could follow on with .withScope(), .withId() or .eagerLoad() if we wanted. In most cases, defaults from there are sensible or come from annotations on the implementation class (and therefore, rarely need to be overridden).

The primary mechanism for injection is via constructor parameters. In general, annotations are not necessary on those parameters any more, and Tapestry will find the correct object or service automatically (primarily by matching on parameter type).

Service builder methods are still useful for when a service involves more than just instantiating a class, such as registering it for some kind of notification from another service:

    public ComponentClassResolver buildComponentClassResolver(ServiceResources resources)
    {
        ComponentClassResolverImpl service = resources.autobuild(ComponentClassResolverImpl.class);

        // Allow the resolver to clean its cache when the source is invalidated

        _componentInstantiatorSource.addInvalidationListener(service);

        return service;
    }

The autobuild() method will construct an instance, performing necessary injections. We can then perform any additional realization before returning it.

The end result has been to simplify and minimize the amount of code in the module builder classes.

Tapestry IoC now supports services that do not have a service interface; the actual type is used as the service interface and the service is not proxied: it is created on first reference and can't have interceptors. Normal services are proxied on first reference, and only realized (converted into a full service with a core service implementation and interceptors) on first use (the first method call).

In a step away from the code, we are running a vote to add Dan Gredler as a Tapestry committer. I expect that to run successfully, and I also expect to add a few more people to the roles soon.

I'm getting very excited. Things are coming together nicely (but never fast enough).

Tuesday, April 03, 2007

Tapestry 5 IoC from a Guice Perspective

So Guice has a few good ideas, and with Tapestry 5 still in a fluid state, I've absorbed a few of them into Tapestry 5 IoC. At the core, is the key ObjectProvider interface:

public interface ObjectProvider
{
    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator);
}

Implementations of this interface plug into a chain-of-command within Tapestry and, via the AnnotationProvider, can query for the presence of annotations to help decide exactly what is to be injected. A lot of Guice's examples, things like the @Blue service, are easily replicated in Tapestry 5 IoC, there's just not a lot of need for it. I supposed it would look something like:

public class BlueObjectProvider {
  private final Map<Class,Object> _blueObjects;

  public BlueObjectProvider(...) { ... }

    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator) {
      if (annotationProvider.getAnnotation(Blue.class) == null) return null;

      return objectType.cast(_blueObjects.get(objectType));
  }
}

Implicit here is a service configuration of implementations, keyed on object type, used when the @Blue annotation is present. Here's what the module would look like:

public class BlueModule {

  public ObjectProvider buildBlueProvider(Map<Class, Object> configuration)
  {
    return new BlueObjectProvider(configuration);
  }

  public void contributeMasterObjectProvider(
    @InjectService("BlueProvider") ObjectProvider blueProvider,
    OrderedConfiguration<ObjectProvider> configuration) {
    
    configuration.add("Blue", blueProvider);
  }

  public void contributeBlueProvider(MappedConfiguration<Class, Object> configuration,
    @InjectService("MyBlueService") Service myBlueService) {
    configuration.add(Service.class, myBlueService);
  }
}

public class OtherModule {

  public OtherService build(@Inject @Blue Service service) {
    return new OtherServiceImpl(service);
  }
}

On the one hand, this is more verbose, since its somewhat more procedurally based than Guice's approach, which is pattern based. On the other hand, it demonstrates a couple of key features of Tapestry 5 IoC:

  • Service construction occurs inside service builder methods; there's no layer of abstraction around service instantiation, configuration and initialization ... you just do it in Java code. There simply isn't a better language for descibing instantiating Java objects and invoking Java methods than Java itself.
  • Dependency injection is a concern of the module not the service implementation. The special annotations go in the module class, and the service implementation class is blissfully unaware of where those dependencies come from or are identified and obtained.
  • Dependencies are passed to the service builder methods, which do whatever is appropriate with those dependencies; in many cases, the dependencies are not passed to the instantiated class, but are used for other concerns, such as registering the new service for event notifications from some other service.
  • The contributeBlueProvider() method can appear in many different modules, and the results are cumulative. This allows one module to "lay the groundwork" and for other modules to "fill in the details". And, again, if this style of injection ("flavored" with the annotations) really became popular, it would be easy to integrate it into the central Tapestry 5 IoC module so it could be leveraged everywhere else.

In fact, in the OtherModule class, you can see how the injection lays out once the groundwork is prepared: Just an @Inject qualified with @Blue and you're done. Again, I can't emphasize enough how well moving construction concerns into the service builder methods works; it gives you complete control, it's unit testable in a couple of different ways, it keeps the service implementations themselves clean (of dependencies on the IoC container -- even in the form of annotations). It also keeps explicit what order operations occur in. HiveMind chased this with ever more complex XML markup to describe how to instantiate, configure, and initialize services.

That last feature, the interaction of multiple modules discovered at runtime, is the key distinguishing feature, and the one that's hardest to grasp. For me, it is the point where an IoC container transitions from an application container to a framework container. In an application container, you explicitly know what modules are present and how they will interact. It's your cake, starting from raw flour, sugar and eggs. Nothings in the mix that you didn't add yourself.

With HiveMind and Tapestry 5 IoC, the goal is to build off of an existing framework, and its interwoven set of services. The framework, or layers of framework, provide the cake, and you are trying to add just the icing. Configurations are the way to inject that icing without disturbing the whole cake, or even knowing the cake's recipe.

So Guice is a good kick in the pants, and Tapestry 5 IoC has evolved; the old @Inject annotation, which used to include a string attribute to name the thing to inject, has been simplified. The @Inject annotation is still there, but the string part is gone. Tapestry primarily works from the object type to match to the lone service implementing that interface. When that is insufficient, there's the MasterObjectProvider and Alias service configurations, which provide plenty of room to disambiguate (possibly by adding additional annotations to the field being injected).

The big advantage is type safety; refactor the name of a service interface and you'll see little or no disruption of the service network, because dependencies are almost always expressed in terms of just the (refactored) service interface, rather than any additional layer of "logical name".

I think there's a growing issue with Google-worship. It was interesting and disturbing to see so many people announce "Spring is dead! Long live Guice!" on various blogs (and I'll even admit to a little envy ... how come Tapestry 5 IoC doesn't generate this kind of interest?). Guice itself is pretty useless by itself, just as Spring's IoC container is, by itself, useless. The value of Spring is in what's built on top of the container: Wrappers for JDBC, Hibernate, JPA, JMS and every other acronym you can name. Transaction support and interceptors. And, more recently, the really solid AspectJ layer. If Guice wants to be a Spring-killer, or even a real player in the IoC space, it needs to embrace and extend Spring: make it easier to use those Spring integrations than it would be using Spring natively.