Interfaces are no longer required with Java EE 6, so
you can use them more consciously for the realization of business logic.
Introduction
Since Java Platform,
Enterprise Edition 6 (Java EE 6), interfaces are no longer required by the
container in order to realize common use cases. Transactions, security, custom
aspects, concurrency, and monitoring are also available for plain classes without
any interfaces. Java EE 6 made interfaces meaningful again. Since they are no
longer required by the platform, you can use them more consciously for the
realization of business logic. An interface becomes a vehicle for encapsulation
or abstraction, as it was originally intended to be:
“Interfaces are used
to encode similarities which the classes of various types share, but do not
necessarily constitute a class relationship. For instance, a human and
a parrot can
both whistle; however, it would not make sense to
represent Humans and Parrots as subclasses of
a Whistler class. Rather they would most likely be subclasses
of an Animal class (likely with intermediate classes), but
both would implement the Whistler interface.” [http://en.wikipedia.org/wiki/Java_interface]
Unfortunately,
interfaces are not used within the context of Java EE as a tool for encoding
similarities; they serve as reassurance for possible, but unlikely, future
enhancements. Extensive use of interfaces derives from a belief that they might
be helpful in the future.
In theory, you could
make everything extensible with interfaces. If you used interfaces for
everything, you could swap the implementations without recompiling the clients.
However, such extensibility comes with a price. The number of artifacts will
double and you will have to introduce a configuration facility.
The purpose of a
configuration is the management of associations between interfaces and their
implementations. At least the unique name of the interface and the name of the
interface’s realization have to be maintained in the configuration, and that
leads to duplication. Exactly the same information is stored in the interfaces
as well as in classes. Every change to the fully qualified name of the class or
interface has to be performed atomically. The source files and the
configuration have to be changed at the same time.
When You Cannot Find a
Name
When the only motivation
for the introduction of an interface is not the abstraction of an already
existing implementation, but rather for a future extension, you will run into
naming clashes between the interface and its implementation. You will not be
able to name the interface and its implementation after their responsibilities.
A common workaround to this problem is the addition of an "I" prefix
to the interface or an Impl suffix to the implementation. For example, you will
see code such as Communicator communicator = new CommunicatorImpl();.
Such naming does not
help you to recognize the responsibilities at all. Rather, it emphasizes
already known facts: A class is, of course, an implementation of an interface
declared after the implement's keyword.
It gets even worse,
though. Although the impetus behind such a naming scheme is the introduction of
future extension points, it is impossible to introduce another implementation
with reasonable naming. (CommunicatorImpl2 is not a reasonable name.) So if you
cannot name the implementation or interface properly, do not introduce an
interface.
Back to the Natural
Contract
The contract of a class
comprises all of its public methods. The public methods are intended to be used
by their clients. The no-interface view of an Enterprise JavaBeans 3.1 bean is
defined exactly as follows in Chapter 3.4.4 of the EJB 3.1 specification (JSR 318):
“…A Session Bean’s
no-interface view is a variation of the Local view that exposes the public
methods of the bean class without the use of a separate business interface…”
All private methods are
hidden. Methods with package-private and protected visibility are visible only
to classes in the same package and they are usually accessed only for test
purposes. A JUnit test class resides in the same package as the “Class Under
Test” (CUT) and mocks-out the inconvenient references, usually accessing the
package-private or protected fields directly.
As Simple as Possible…
There is also no
interface required to expose an existing EJB 3.1 bean via Representational
State Transfer (REST) (see Listing 1). A MessageResource is an EJB 3.1 bean
directly exposed as Java API for RESTful Web Services (JAX-RS) endpoint.
@Path("messages")
@Stateless
@Produces(MediaType.APPLICATION_JSON)
public class MessagesResource {
@Inject
Communicator communicator;
@GET
public List<Message>
allMessages(){
return
communicator.getRecentMessages();
}
}
Listing
1: A Sample REST Endpoint
You could annotate
interfaces with JAX-RS and hide some JAX-RS annotations this way. However, such
a separation is only nice in theory. In practice, a more complex application
will need access to JAX-RS specific classes, such as javax.ws.rs.core.UriInfo,
javax.ws.rs.core.UriBuilder, or javax.ws.rs.core.Response, and it will be
dependent on the JAX-RS API anyway. In fact, in all non-trivial applications,
it is a good idea to split the protocol-dependent part (SOAP, JAX-RS, CORBA,
and so on) and the pure boundary part into two independent classes.
Although the class
Communicator is injected directly, it can be still intercepted. Java EE 6
interceptors (see Listing 2) work equally well with interfaces and with plain
Java classes.
public class CommunicationSniffer {
@AroundInvoke
public Object sniff(InvocationContext
ic) throws Exception{
Object result = ic.proceed();
System.out.printf("Method %s
returned %s",ic.getMethod(),result);
return result;
}
}
Listing 2:
Interface-less Method Call Interception with CommunicationSniffer
You only have to declare
an interceptor in an annotation or XML descriptor to apply cross-cutting
concerns to your class. You could also implement an interface and inject it,
but you don’t have to do so.
@Interceptors(CommunicationSniffer.class)
public class Communicator {
public List<Message>
getRecentMessages(){
return new
ArrayList<Message>(){{
add(new
Message("first"));
add(new
Message("second"));
}};
}
}
Listing 3: An
Intercepted POJO
A GET request of the URI
[WAR_NAME]/resources/messages is processed by the method allMessages, which
delegates to the injected Communicator#getRecentMessages() instance. The call
is intercepted by the CommunicationSniffer interceptor, which writes the
following string to the standard-out stream:
Method public
java.util.List
com.tm.patterns.nointerfaces.control.Communicator.getRecentMessages()
returned
[Message{content=first}, Message{content=second}].
No interface is required
for the serialization of the Java Architecture for XML Binding (JAXB)-POJO
Message either (see Listing 4). Interface introduction for the Message entity
would be, in fact, rather confusing in this case. There are no methods to be
specified in a contract.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Message {
private String content;
public Message(String content) {
this.content
= content;
}
public Message() {}
}
Listing 4: An Entity
Message with JAXB Annotations
…But Not Simpler
Now the MessageResource
is directly dependent on the implementation of the class Communicator. With a
direct dependency on the implementation, the Communicator cannot be easily
swapped out anymore. You will have to change the MessageResource code to
replace Communicator with other implementations.
Let’s introduce another
variation of the Communicator class called ConfidentialCommunicator. It returns
a different message and is not intercepted by the CommunicationSniffer
interceptor (see Listing 5).
public class ConfidentialCommunicator {
public List<Message>
getRecentMessages(){
return
new ArrayList<Message>(){{
add(new Message("top secret"));
}};
}
}
Listing 5: An
Alternative Communicator
We could inject both the
ConfidentialCommunicator and the Communicator at the same time and decide
inside the MessageResource which instance to use. Such a solution would be
unnecessarily complex, hard to understand, and so also hard to maintain.
The first Communicator
version was intercepted by the CommunicationSniffer and didn’t care a lot about
privacy. So let’s rename it to PublicCommunicator. Hence, we have a
ConfidentialCommunicator and a PublicCommunicator with different
responsibilities, but identically signaturing them is a natural next step to
introducing an interface to hide the implementation specifics and define a
common contract.
The MessageResource does
not care about the specific implementations. It only wants to serialize a
List<Message> into a JavaScript Object Notation (JSON) string. The name
of the class happens to be a good name for an interface as well: just
Communicator.
Interestingly, the
MessageResource class remains unchanged after the interface introduction:
public class MessagesResource {
@Inject Communicator communicator;
//…
}
Actually,
MessagesResources shouldn’t care about the implementation specifics. It is
interested only in a single method: getRecentMessages. It could be implemented
by a single public class or by an interface that hides different
implementations. Both cases look exactly the same from the MessageResources
perspective.
Flexibility Comes with a
Price
Now we have a
Communicator interface directly injected into the MessageResource EJB 3.1 bean.
The existence of an interface implies multiple implementations: ConfidentialCommunicator
and PublicCommunicator.
Now we have to choose
between these implementations. The direct injection of the Communicator
interface does not work anymore without configuration, and it causes the
following error:
org.jboss.weld.exceptions.DeploymentException:
WELD-001409 Ambiguous dependencies
for type [Communicator]
with qualifiers [@Default] at injection point
[[field] @Inject com.tm.nointerfaces.boundary.MessagesResource.communicator].
Possible dependencies
[[Managed Bean [class
com.tm.nointerfaces.control.PublicCommunicator]
with qualifiers
[@Any @Default], Managed
Bean [class
com.tm.nointerfaces.control.ConfidentialCommunicator]
with qualifiers
[@Any @Default]]].
We can disambiguate the
dependency by deactivating one of the managed beans. Denoting a managed bean
with the javax.enterprise.inject.Alternative annotation deactivates it:
@Alternative
public class PublicCommunicator implements
Communicator{}
Now the relation is
one-to-one. The Communicator interface can be directly injected into the
MessagesResource. Invocation of the URL generates the following output, which
is generated by the ConfidentialCommunicator implementation:
{"message":{"content":"top
secret”}}
You have to move the
annotation from the PublicCommunicator to the ConfidentialCommunicator to
re-activate the PublicCommunicator, and you have to recompile the whole
application to switch between implementations.
You could also
deactivate both and activate the implementation of your choice in beans.xml
(Listing 6).
<beans>
<alternatives>
<class>com.tm.nointerfaces.control.ConfidentialCommunicator</class>
</alternatives>
</beans>
Listing 6: Alternative
Activation in beans.xml with Fully Qualified Managed Beans
Now the fully qualified
name of the class has to be maintained in the XML file as well as in the source
code. Hierarchy changes or even simple renaming have to be performed in two
places at once. Contexts and Dependency Injection (CDI) stereotypes provide an
elegant solution to the data replication problem. Instead of repeating the
fully qualified class names only a tag interface is specified (see Listing 7).
<beans>
<alternatives>
<stereotype>com.tm.nointerfaces.control.Confidential</stereotype>
</alternatives>
</beans>
Listing 7: Activation of
Classes Annotated with a Stereotype
A stereotype is an
annotation denoted itself with the @Stereotype meta-annotation (see Listing 8).
@Alternative
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Confidential {}
Listing 8: An
@Alternative @Stereotype
Now you can deactivate
managed beans just by using the stereotype instead of the raw @Alternative
annotation (see Listing 9).
@Confidential
public class ConfidentialCommunicator implements
Communicator {}
Listing 9: Deactivation
with a @Stereotype
All classes annotated
with the @Confidential stereotype can be activated at once by specifying the
fully qualified name of the stereotype instead of configuring the fully
qualified class name of the managed bean. Moving a stereotype to another
package or renaming it results in the same hassle: the Java source and the
contents of the beans.xml file have to be maintained at the same time.
Stereotype refactorings, however, are far less likely.
You can also annotate
several classes with a single stereotype and activate them at once. The 1:n
relation between a stereotype and managed beans makes a stereotype-based
solution more maintainable. You don’t have to maintain all the fully qualified
names of the managed beans in beans.xml.
Let the User Decide
Both the provider and
user are able to configure the injection. Instead of aimed activation and
deactivation of managed beans with beans.xml, annotations, or both, we can
decide at the injection point which implementation to choose.
The ambiguity has to be
resolved with a custom qualifier annotation for this purpose. The @Qualifier
annotation is similar to the @Stereotype annotation, but it is used to resolve
an ambiguous dependency by marking both parts with the same annotation with the
same elements.
@Stereotype is a mix of
a tag interface and a macro. You can annotate a stereotype with several
annotations and make a class declaration to be more concise. All the used
annotations in the stereotype annotation are expanded on the class level. In
the previous example, we used the @Alternative annotation as a meta-annotation.
Instead of the
@Confidential stereotype, we mark each implementation, as well as the injection
point, with the @Confidentiality qualifier (see Listing 10).
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Confidentiality {
Level value();
enum Level{
STRONG, WEAK
}
}
Listing 10: A Custom
Qualifier for Dependency Resolution
The Confidentiality
qualifier comes with an embedded enumeration, Level, as well. The value of the
Level enumeration is also used as matching criteria. Because no default value
is specified, you will have to choose the value by applying the annotation.
The
ConfidentialCommunicator is annotated with the STRONG Level value:
@Confidentiality(Confidentiality.Level.STRONG)
public class ConfidentialCommunicator implements
Communicator {}
And the
PublicCommunicator is annotated with the WEAK Level counterpart:
@Confidentiality(Confidentiality.Level.WEAK)
public class PublicCommunicator implements Communicator{}
The Communicator user
can now decide which implementation to use by setting the value of the Level
enumerator (see Listing 11).
public class MessagesResource {
@Inject @Confidentiality(Confidentiality.Level.WEAK)
Communicator communicator;
//..
}
Listing 11: A Qualifier
at the Injection Point
The user is decoupled
from a particular implementation and decides by specifying a qualifier at the
injection point regarding which implementation to use. In contrast, with
stereotypes, qualifiers are the most intrusive approach. The user’s code has to
be extended with the qualifier annotation and re-compiled for every change. On
the other hand, custom qualifiers are easy to maintain, because the compiler prevents
any misspellings.
The Runtime Choice
You can even explore all
implementations of the interface and decide which implementation to use at
runtime. An injected javax.enterprise.inject.Instance provides the most
flexibility. You can query the injected Instance to determine whether the
dependencies are ambiguous or unsatisfied and iterate over all implementations.
With the @Any qualifier,
all implementations of the Communicator interface are discovered regardless of
whether they are annotated with a custom qualifier (see Listing 12).
@Path("messages")
@Stateless
@Produces(MediaType.APPLICATION_JSON)
public class MessagesResource {
@Inject @Any
Instance<Communicator>
communicatorInstances;
@GET
public List<Message> allMessages(){
System.out.println("--isAmbiguous:
" + communicatorInstances.isAmbiguous());
System.out.println("--isUnsatisfied:
" + communicatorInstances.isUnsatisfied());
List<Message> allMessages = new
ArrayList<Message>();
for (Communicator communicator : communicatorInstances)
{
allMessages.addAll(communicator.getRecentMessages());
}
return
allMessages;
}
}
Listing 12: Iteration
Over All Realizations at Runtime
This is the JSON string
generated by the Listing 12:
{"message":[{"content":"first"},{"content":"second"},{"content":"top
secret”}]}
Interfaces on Demand
Java EE 6 does not
require you to use interfaces for the implementation of JAX-RS, EJB, CDI, or
Java Persistence API (JPA) components. You can start with direct injection of
the class. There are no sacrifices to be made. Interceptors, transactions,
security, monitoring, threading, and dependency injection all work equally well
without interfaces.
If you name the
implementation after its business responsibilities, the user of the injected
class will have a hard time telling whether it is a concrete class, an abstract
class, or an interface. Naming conventions such as Impl suffixes are
counter-productive for that reason. Such naming conventions are also absolutely
superfluous, because they emphasize obvious facts.
Sometimes, you will have
to introduce another class and abstract both implementations with a formal
contract--an explicit Java interface. This is only a minor refactoring. You
will have to do the following:
- Rename the original implementation after its responsibilities. (We renamed Communicator to PublicCommunicator in the earlier example.)
- Introduce an interface with the name of the original implementation (Communicator). The original class has to implement this interface.
- Name the other class after its business responsibilities and implement the interface.
If you need more
flexibility, you can introduce stereotypes or qualifiers or query the framework
at runtime (with javax.enterprise.inject.Instance). In any case, it will be
only a minor refactoring.
Probability-Driven
Decisions
There is nothing wrong
with the abstraction of every implementation with an interface if such an
approach can be clearly justified, but interfaces become dubious when you have
to introduce artificial naming conventions to avoid name clashes. Using
prefixes such as “I” or suffixes such as IF or Impl should be considered to be
code smells.
Interfaces should be
introduced only as a contract for already existing classes, for the realization
of Strategy or Bridge patterns, or when you need to design an API, such as Java
Database Connectivity (JDBC), Java Message Service (JMS), and so on. In typical
business applications, this occurs in only a fraction of all cases.
Usually injecting a
class without any interface is good enough. In the unlikely, worst-case
scenario, you will have to introduce an interface afterwards. Such partial refactoring
are cheaper to perform afterwards than doubling the number of artifacts by
implementing an interface for every significant all the time class.
Binary incompatible
changes are not an issue in enterprise applications either. In a Continuous
Integration (CI) environment, all the code will be recompiled, retested,
repackaged, and redeployed at every commit anyway. Reasoning that “it might be
needed in the future” or implementing premature extensions for every possible
use case is no longer viable in Java EE 6. It is trivial to extend well-written
and concise code after the fact with only minor refactoring.
See Also
- Wikepedia: Strategy pattern
- Wikepedia: Bridge pattern
- Interface information in the Java Language Specification
- Interface definition: http://en.wikipedia.org/wiki/Java_interface
- EJB 3.1 specification (JSR 318)
- Real World Java EE Night Hacks—Dissecting the Business Tier (see “Easy Extensibility for the Unlikely Case” on page 102).
- Adam Bein Oracle article CDI on Demand.
No comments :
Post a Comment