You probably read the news the other day. There have been very few reactions so far apart from Phillip reporting the news and Paul making an allusion to it (It's Alright, Ma)
There would have been reasons to take a perspective on Nuxeo's technology switch instead and ask oneself the question:
For Zope, what can be learned from the Java technologies?
Admittedly it sounds as a provocative question, knowing that Zope pioneered great innovations at a time when Java-based solutions were still in their infancy.
So here is my take on it, not about comparing Python and Java (the languages), but about the rest, which is what really matters, isn't it?
1) The importance of the IDE
This is a crucial point that some commentators of the web framework
comparison video mentionned, saying in substance "But wait ... people use an IDE when developing with JEE".
I have personally always used 'vi' when developing with Zope2 and Zope3. I have tried different IDEs for python (Wing IDE, Eclipse pydev with extensions, BOA constructor) but always went back to vi, because I noticed that what I wanted was not really another editor but an integrated development environment and there is no such thing as 'half-integrated' environment. Julien mentionned this in his blog.
Shane also posted about Zope 3 IDE Considerations about a year ago.
It true that developing in Java without an IDE takes more time than it would have taken in Python, but that's not what people do anyway.
The Eclipse IDE in its most basic form provides:
- integrated unit tests
- automatic Java style formatting using templates (just hit CTRL+SHIFT+F)
- the automatic import of Java packages with one click (and hit CTRL+SHIFT+O to clean up and sort imports in alphabetic order).
- advanced support for refactoring: renaming files, packages, or moving files between packages updates all references automatically
- non initialized and unused local variables are highlighted
- the deprecated classes of a package are marked as such
- syntax errors are highlighted, but also programming errors such as
- "not all methods have been implemented, click here to add unimplemented methods or declare this class as abstract"
- "cannot reduce the scope of this method from public to private"
- "this class needs a constructor, click here to add the constructor"
Basically this means that when the code is getting compiled, 99% of the basic errors have been removed. The rest are real design errors (null references, ...)
This makes development four to five times faster, indeed. One also learns language features by simply following the IDE's suggestions.
But most of all, it makes development fun.
2) The importance of stable APIs
One important aspect when using external libraries in your own code is to know about the API's contract:
- how stable is the API? will half of the classes be deprecated or the packages be moved to another location in the next release? Basically: what is the upgrade path?
- how is the API supposed to be used or extended, what it public what it private?
I have felt some frustration at some Java packages that just would not let me subclass because the class was declare as final, or that would not let me use a method because it was private. But thinking about it afterwards, it simply means that:
"this is not the way the API should be used"
In Python I would have just subclassed or used the private method anyway.
The bottom-line is that there is only one chance for a developer to get the API right, because other developers expect it to be stable and easy to extend.
And no, the deprecation feature is not a tool available to developers for doing cosmetic refactoring or for renaming packages.
3) The importance of standards
What is involved in "getting the API right"?
My conviction is that it is better to get developers to agree on a specification than having the code implemented.
It involves agreeing on a specification, i.e. selecting the list of features and use cases needed by anyone interested in using the API, creating the specification either on paper or by declaring interfaces, and letting anyone interested implement the API, or to provide a default implementation.
Lots of packages are being added in the Zope3 repository without proper reviewing, implying: "we find this useful, hence someone else may find it useful too". This is relatively harmless for utility packages that have a limited scope, but for framework foundation modules this is a problem.
It is perfectly fine to have 3 or 4 different implementations of the same specification that provides a given feature. But having 3 or 4 different specifications of a same basic feature because they were hurried in simply means a lack of concertation.
package naming conventions
Concerning package naming, the standard way in Java is to use namespaces like 'java' or 'javax' for packages that implement standards, and to use fully qualified namespaces for packages with proprietary features.
For instance, in Zope3 there would be:
- zope.component
- zope.util
- ...
for the standard components and:
- org.zope... for Zope community packages
- com.zope.... for Zope Corporation packages
- org.plone...
- org.nuxeo...
- com.infrae...
In this way, it would make it clear what packages can be considered as standard, foundation packages and which are vendor-specific.
Otherwise as a developer I may well start using a zope.abc package to learn 3 months later that the package was just the result of an experiment, and that it will be replaced by a "better" implementation.
4) The Design Patterns
This is an interesting topic.
There are no such things as adapters, factories, utilities, events in the Java language, these are just design patterns.
Quoting the "design pattern" definition from the link cited above:
"A design pattern systematically names, motivates, and explains a general design that addresses a recurring design problem in object-oriented systems. It describes the problem, the solution, when to apply the solution, and its consequences. It also gives implementation hints and examples. The solution is a general arrangement of objects and classes that solve the problem. The solution is customized and implemented to solve the problem in a particular context."
The most important part of the definition I think is the last sentence:
"The solution is customized and implemented to solve the problem in a particular context"
In other words, in some cases using the Adapter pattern may be a good idea while in other cases, it may be a bad idea. One cannot know the solution until the problem to be solved has been described.
My impression is that the 4 basic patterns promoted in the zope3 architecture as the "new religion" are predefined solutions to problems that are yet to be identified.
I started questionning the need for adapters when trying to use to equivalent
in Java, for example:
If I have an object and I want to know its size, I would in Zope3 do something like:
>>> a = MyObject()
>>> size = ISize(a).getSize()
in Java that would become:
First the object adapter:
public class SizeAdapter {
Object object;
public Size(Object object) {
this.object = object;
}
Integer getSize() {
Integer size = null;
// compute the size from the object
return size;
}
}
then the adapter would be used as:
Object a = new SomeObject();
Integer size = new SizeAdapter(a).getSize()
the difference is that
ISize(a)
in zope3 does an adapter lookup based on adaptee's type. But otherwise the idea is the same: the adapted object is passed as a context parameter to the adapter's constructor and gets stored in the adapter's created instance.
In fact, one probably would not do it that way in Java (that is, using the Object Adapter pattern), because during every use of the adapter a new object is instantiated, that needs to be garbage collected.
Probably one would use a static method instead:
public class SizeCalculator {
public static Integer getSizeOf(Object object) {
Integer size = null;
// compute the size
return size;
}
}
and one would use the "size calculator":
Integer size = sizeCalculator.getSizeOf(a);
which is to say that only one instance of the "SizeCalculator" will be created.
One may also register different "SizeCalculator" factories for different types of objects, or alternatively one could put the entire logic inside the getSize() method. But again it all depends on the nature of the problem to be solved:
For instance, if the "SizeCalculator" is used inside an internal API only, there is no need to make it pluggable to make it support calculating the size of any type of object, because that part of the code is not supposed to be exposed or be extended.
One could also use the Class Adapter pattern or another pattern, or use no specific pattern at all.
Conclusion: the Component Architecture is not a universal solution for particular problems.
5) The Component Architecture
The main question is: what problem does the Component Architecture solve?
But first let us ask: in a platform or in an application, do we want components?
Yes, we want separation of concerns, we want different logical layers that separate storage, business logic, presentation, we want core components for the core functionality. We want to be able to extend the platform without modifying the existing code. we want extension points, etc.
But how "big" do we want these components to be, do we want micro-components?
No, because it takes extra configuration to connect components together, and if the separation occured on a "micro level" this would add complexity only to the application. And it is not necessarily the place where the application or the platform is supposed to be extended.
What is needed in a platform is a "Plugin Architecture" at a high abstraction level, i.e. the ability to define "extension points" at some strategic places of the platform (not everywhere) and give to developers the ability to register their own plugins by using a high-level API.
Conclusion: the Component Architecture does not replace high-level API design (directory service, storage abstraction, presentation layer, document model, ...) The fact that there are lots of components does not imply that the platform is easy to plug anything into.
6) ZCML is not XML
The idea with ZCML is to move the configuration part of the application away from the Python code. So in order to reconfigure a component one would not change the existing application.
Technically this is true. Practically however, one is required to understand how the code works in order to modify a ZCML configuration file, so this only adds an extra indirection compared to modifying the existing Python code directly.
The same level of complexity appeared in J2EE before Annotations where introduced into the Java language. Before that XML configuration files were used to configure all components. This is what comes out clearlyv in the web framework comparison video cited ealier.
With Java5, annotations where introduced that make it possible to configure components entirely in the code:
@Name("My widget")
@Type(SOME_WIDGET_TYPE)
class MyWidget implements Widget {
...
}
Hibernate, EJB3, JBOSS Seam use annotations a lot, and there aren't hundreds of XML files anymore.
There are cases however where XML-based configuration makes sense:
1) when the configuration features are not about configuring individual components, but about configuring the entire application or an entire module instead, for instance to switch to "debug mode", or to configure a presentation framework, or to configure the deployment of an application.
Basically the idea is that actions that are done often should require little registration code in XML, while actions that are performed only once may well require a lot of XML configuration.
2) or when the XML configuration does not refer to any specific Java component, for instance when describing a series of rules, or a workflow definition, or a page layout, or page navigation rules, etc...
Unfortunately ZCML only allows one level of XML nested structures which makes that type of configuration cumbersome, because even more indirections need to be created, instead of simply writing:
<directive1>
<directive2>
<directive3 ... />
</directive2>
</directive1>
7) The Presentation Layer
Well, presentation is not only about presenting data, it mainly about managing the data that is to be presented. That's called "context management".
There are different types of contexts (application, session, request, ...) that can contain data. Each context has a different scope and a specific lifecycle.
For instance, the application's data is present as long as the application runs, the session data exists until the user closes the session or when the session expires, and the request's lifespan ends when the response has been served.
In any case a same presentation template will probably need to get access to many different pieces of data, for instance in this template:
<h1>Documents of #{currentUser}</h1>
<h2>All the documents viewed previously:</h2>
<ui:repeat="#{documentHistory}" var="item">
<span>#{item/title}</span>,
</ui:repeat>
<h2>Current document</h2>
<p>#{currentDocument.content}</p>
.. a lot of data from different sources needs to be collected before anything can be displayed.
In Zope3 this is what a "view" does. A view adapts the context and the request to create a transient object that the presentation template will be able to use to get access to the presentation data. So the view's function is really to collect data in the first place.
I have had trouble sometimes in Zope3 to decide which object is the "context" object. This is when the presentation is not concerned with presenting a single object but it involves presenting several different objects. In that case there is no obvious one and single context.
In any way, the point is that you don't need view objects really. All that is needed is a simple way to get access to contextual data from inside a presentation template.
This can be done by letting the presentation framework find out which components are used in a template and let it create or look up the components for you.
This is what JSF and JBoss Seam do.
The result is that there is no need to collect data, because the framework does it for you and manages the lifecycle of these objects automatically depending on there scope. Instead it is possible to refer to the components directly from inside the template.
Seam also introduces two new context scopes called "conversation context" and "page context".
A "conversation context" is associated to a page flow, it exists as long as the user has not performed a given final action. This is useful when booking flight tickets, or when shopping items. Sometimes several conversation contexts need to be created in parallel, and the framework does it for you.
The "page context" exists for a given page. When the user navigate back to a same page you the context becomes available again.
There is also "Seam remoting" that makes Ajax remote call completely transparent from javascript, as if all Java methods where directly usable in Javascript.
Some of this is in fact inspired from Ruby-on-Rails.
8) The "pythonic" trip
What is "pythonic" what is not? but who cares basically as long as you have a good API and a great framework?
Really, it is better to spend energy on more important things.
9) Balkanization
The balkanization of the Python community is a real issue I think. Maybe this is due to too much focus on the language itself (the language for the sake of the language) and not enough focus on existing standards.
10) Self-criticism
Self-criticism is about being able to see what others did and admit when it is the case that they did some things better; especially taking a look at JEE, JBoss, Eclipse, or Ruby-On-Rails, Turbogears, Django, etc.
JBoss Seam for instance was created as a Java response to the Ruby-on-Rails' success.
What has the Zope Community's response been to RoR?
- "that's just because they have a better web site"
- "that's just hot air and marketing, all that we need is a better web site"
Maybe it is time for constructive self-criticism?