Extensible/adaptable Java EE application: interfaces vs interceptors and decorators
Asked Answered
C

5

11

We are currently in the process of analyzing the technical requirements for a new version of a Java EE application. It's Java EE 6, but moving onto 7 is an option. Until now this was a single application, one EAR, offering well-defined functionality. However, it needs to become possible to define some functionality in a very specific way based on implementation project and/or customer.

For example, some customers are going to have very specific security constraints. A list of messages processed by a framework can be shown. For one customer, it's okay to have users see everything, but another customer would like to show only certain types of messages based on the user's group. That's an example of defining an implementation in a context-specific way, adapting the core functionality. Another possible requirement is that some customers will want to expand on the given functionality, adding new possibilities. So that's the extensible part.

Because of this, it is necessary to have an architecture that defines generic functionality but has pluggable parts, as well as a possibility for extension. For some aspects I have a rough idea of how this could be handled. This question has an answer that would work perfectly for the presentation layer, which we'll do in JSF 2: How to create a modular JSF 2.0 application?

I'm less certain about the business logic layer, security etc. My initial idea would be to define interfaces (a couple of facades) and find the implementation at run-time or deployment time. In much the same way as the service provider mechanism. A default implementation can be offered, with the possibility of defining a custom one. This does feel like a sort of Java SE solution, and I'm wondering if I'm just applying concepts that I'm familiar with from that context and if there's nothing better for EE. I think the @Alternative annotation serves a purpose like this.

Another possibility might be to use interceptors and decorators. I'm not certain to what extent interceptors are useful outside of logging, auditing and other things that don't touch the core business logic. Decorators seem suitable for allowing an implementation to be extended with custom functionality, and perhaps also the pluggable parts.

Can someone offer some insight into which solution is best for which part of this challenge? Should I combine these methods for various parts of the problem domain? Are there possibilities I'm not seeing?

An important requirement: we want to make it possible to keep the code that is specific to a customer/project separate. We don't want to have a complete version of the application under version control for each implementation, as that would become a maintenance nightmare fast. Ideally, it would also not be necessary to build this as a monolithic EAR, but be capable of adding pluggable parts to some lib folder or deploy them separately.

Client answered 15/1, 2014 at 17:19 Comment(0)
J
13

Frankly speaking there are not so many good approaches to make your application modular (I think this is the proper way to rephrase your requirements).

Please also note, there were so many discussions, failed attempts and strange designs to simplify development of modular applications, but still we have so many monolithic monsters. I've been supporting enough enterprise systems to be frightened till the end of my life.

Initial design is very important and it is not only about basic concepts like interfaces. Please don't get me wrong. Interfaces are important, but I would prefer to use a bit different terminology -- contracts or extension points

enter image description here

Let's imagine you've defined extension points properly. What will you do next? I'd say eventually you'll implement some kind of plugin framework. (You can spend an hour to play with JSPF and see how it simplifies development of modular applications and encourages loose coupling).

Bad news, this solution might be a bit dangerous for any production system. Mainly because complex class loading strategy (introduced by this approach) might cause memory leaks. So you'll find yourself analyzing memory dumps quite soon. Class-loaders and all related stuff became a bit complex :)

enter image description here

Anyway, let's assume you've solved all issues with class loading, but what about plugins/modules life-cycle? In any loosely coupled system you'll need to to strictly define how modules will interact. Plugin framework will not entirely solve this problem.

Eventually you'll come up with modules life-cycle in order to define all important aspects. For example:

enter image description here

My suggestion is to avoid reinventing the wheel. OSGi might be a good solution for you. Please also note OSGi is good, mature and provides a lot stuff out of the box, but it is also somewhat complex:

enter image description here

So you'd definitely need some time to investigate it a bit deeply. Please also check the following : What benefits does OSGi's component system provide you?

Another suggestion is to check any existing big software product known to be good and modular (Jenkins, for example).

Update

Ok, taking into the account the discussion below I would suggest the following:

  1. Reed or looks through several relevant books Real World Java EE Patterns Rethinking Best Practices, Real World Java EE Night Hacks Dissecting the Business Tier
  2. Look through EE patterns
  3. Consider using of ServiceLocator
  4. Consider using Injection over ServiceLocator
  5. Decide whether or not you need DAO (some help)
  6. Do not forget to review Java EE 7 Tutorial
  7. Looks through all patterns once again and create small cheat-sheet in order to refresh your mind from time to time, something like the following - example
  8. Design for the future
  9. Finally, create several PoCs in order to prove your approach is good enough.

Conclusion

Sorry for the long answer, but I have to make sure there is a clear and logical point behind it. Your initial intention is 100% correct and good. It is really better to define interfaces/contracts and hide all complexity behind them. This is exactly what OOP is about.

Anyway, the most important task is not to provide good design, but preserve it over time. My suggestion is to enforce good design by using loosely-coupled approach from the same beginning. Service Locator patter is what you actually need.

enter image description here

It will act as a some kind of barrier to save your project and minimize spaghetti-like code. If you identify some problematic module - ok, no problem, it will be properly isolated and easy to replace.

If you have enough confidence skip Service Locator and go for Dependency Injection. Additional reading: Fowler on Dependency Injection vs Service Locator

Jeannajeanne answered 19/1, 2014 at 0:15 Comment(7)
Nice answer. I didn't go with the term modularity because that feels more like making reusable, interoperable components, rather than extensibility and pluggability. But that's kind of subjective. OSGi does often come up here, but I'm somewhat loathe to use it due to its complexity, which is mostly due to things we won't need. It's not necessary to be capable of swapping implementations at run-time and stopping or starting subsystems. Maybe I should have clarified this Java EE app is a front-end for a back-end system (ESB) and does not have a great deal of business logic itself.Client
According to my experience it is almost impossible to keep good and reusable design without any strong motivation force. Any product has to evolve, so it is matter of time to see modules "growing into each other". So it is much better to force loose coupling from the same beginning. BTW, take a look on felix.apache.org I have positive experience with it and it seems to be simple enough. Look through the implementation of Web Console (felix.apache.org/documentation/subprojects/…).Jeannajeanne
The thing is, what you suggest is the perfect solution for those requirements of loosely coupled components, building modules etc. But this is not entirely the point of the question (although it does play a role). This question is precisely about how such loosely coupled modules can be defined so that implementation details are pluggable, and extension possible. While OSGi does offer that, we've found Java EE itself to be sufficient for it. It's what we've built on thus far, and I'm never gonna convince anyone to bring OSGi into it. So I'm after the best Java EE only solution.Client
Ok, I see. Will provide more J2EE specific options.Jeannajeanne
Thanks! Looking forward to it.Client
Please see updated answer. I've collected valuable links and prepared something like a quick reference. Please be advised there are a lot of "J2EE best practices" available. Some of them are outdated, but you might find something useful anyway.Jeannajeanne
Seeing how the bounty still has some days to go, I'm leaving this open for discussion a little while longer. I believe however that your answer has given me all the material needed to make a final decision. Initially we'll try using DI and see what Java EE offers in terms of providing alternatives for default EJB implementations. I might make a follow-up question to ask whether overriding behaviour and extension points should be in facades using standard EJBs, or in the EJBs themselves. But that's another matter. Thank you very much for your great effort!Client
R
2

If one of your goals is to keep the source code separate, I would definitely use the interface approach. You could access your code using a builder based on run-time settings and instantiate the proper class.

I have done this approach in the past, although with one code base.

Rutan answered 15/1, 2014 at 17:23 Comment(2)
I absolutely don't see why this answer has been given a downvote. If someone thinks it is wrong and puts me down the wrong path, EXPLAIN why. Downvotes for a non-trivially wrong answer without explanation are trollvotes to me.Client
My feeling is there should not be any downvoting feature in so. I have seen numerous downvotings which are meaningless.Dworman
U
2

You can define a permission-based design to (not) show to a user (or costumer) some messages. And extend your software functionalities using scripts like bsh or python outside the main application.

Uroscopy answered 20/1, 2014 at 12:43 Comment(2)
Initially though this was a very weird idea, until I noticed it is listed as "fluid logic" in the Java EE patterns Renat posted. While we don't really need that sort of run-time adaptability, this is very interesting and I'm going to remember it for future projects.Client
In my case, our costumers have little different ways to calculate the same rules and may change it at any time, implement these ways in scripts save a lot of work and give us flexibility to not change the software version when they change their ways. The disadvantage is that we do not have good ways to debug scripts, but it may be implemented in future.Potency
L
2

Have you investigated the Strategy Pattern? It sounds exactly like what you are looking for. It allows your code to select behaviors at run time without using instanceof or complex class inheritance. Here is an example and here is the full article:

public interface ShippingMethod {
    public double getShippingCost(double weightInPounds, double distanceInMiles);
}

public class FirstClassShipping implements ShippingMethod {
    public double getShippingCost(double weightInPounds, double distanceInMiles) {
        // Calculate the shipping cost based on USPS First class mail table
    }
}

public class FedExShipping implements ShippingMethod {
    public double getShippingCost(double weightInPounds, double distanceInMiles) {
        // Calculate the shipping cost based on FedEx shipping
    }       
}

public class UPSShipping implements ShippingMethod {
    public double getShippingCost(double weightInPounds, double distanceInMiles) {
        // Calculate the shipping cost based on UPS table
    }       
}

public class ShippingInfo {

    private Address address;
    private ShippingMethod shippingMethod;

    public Address getAddress() {
        return this.address;
    }

    public double getShippingCost(double weightInPounds, double distanceInMiles) {
        return shippingMethod.getShippingCost(weightInPounds, distanceInMiles);
    }
}

In your case, your clients can provide "pluggable" implementations for whatever view/service/model strategy you want to let them alter.

Lothar answered 20/1, 2014 at 16:32 Comment(2)
Upvoted because it is certainly a valid strategy. But actually, this is already kind of implicitly the case with JEE. An EJB will usually have at least one interface (a local one, and optionally remote one), and its implementation. When you use it somewhere by injecting it with @EJB, you refer to the interface. Various EJBs have various tasks (managing JPA, security checks, calculations...). The question is, HOW to best make different implementations pluggable in Java EE. I believed the @Alternative annotation might be one way of doing this. In a sense this already is close to Strategy.Client
@Client thank you for the upvote. Strategy is a very simple, yet powerful pattern. As far as how to best implement pluggability, that would depends on how exactly your customer is going to plug their custom functionality in. Do you ship the product to them and they run it in their environment? That would make introducing overridden functionality really easy, they place their jar on the classpath before yours. If, on the other hand, you host the app, you'd need to introduce some kind of extension points which would call a service method or retrieve some resource (JS file?) on the customer's end.Lothar
D
1

You may provide a REST for all of your features and you can define a CLI (Command line interface) on top of that REST for more secure clients. You use that same REST if you want to build a browser front-end. While extending, You only need to document when a new API (REST call) is designed(added in the go). Under this core, You can go for any level of security by configuring access parameters through j2ee security. I am not a j2ee expert at all, but I am thinking that you are actually looking for a best design pattern for a front-end for a jee app.

JERSEY has clear functionality for marshaling things over wires. If you combine jaxb with this, I think you will be liking it. Club this with JMS for service updates.

Dworman answered 20/1, 2014 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.