DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • How To Connect a Heroku Java App to a Cloud-Native Database
  • How To Build a Multi-Zone Java App in Days With Vaadin, YugabyteDB, and Heroku
  • How Java Apps Litter Beyond the Heap
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)

Trending

  • Security by Design: Building Full-Stack Applications With DevSecOps
  • IoT and Cybersecurity: Addressing Data Privacy and Security Challenges
  • How GitHub Copilot Helps You Write More Secure Code
  • Prioritizing Cloud Security Risks: A Developer's Guide to Tackling Security Debt
  1. DZone
  2. Coding
  3. Java
  4. Modular Java Apps - A Microkernel Approach

Modular Java Apps - A Microkernel Approach

By 
Andreas Haufler user avatar
Andreas Haufler
·
Jan. 23, 12 · Interview
Likes (1)
Comment
Save
Tweet
Share
9.4K Views

Join the DZone community and get the full member experience.

Join For Free
Software Engineering is all about reuse. We programmers therefore love to split applications up into smaller components so that each of them can be reused or extended in an independent manner.

A keyword here is "loose coupling". Slightly simplified, this means, each component should have as few dependencies to other components as possible. Most important, if I have a component B which relies on component A, I don't want that A needs to know about B. The component A should just provide a clean interface which could be used and extended by B.

In Java there are many frameworks which provide this exact functionality: JavaEE, Spring, OSGI. However, each of those frameworks come with their own way to do things and provide lots and lots of additional functionality - whether you want it or not!

Since we here at scireum love modularity (we build 4 products out of a set of about 10 independet modules) we built our own little framework. I factored out the most important parts and now have a single class with less than 250 lines of code+comments!

I call this a microkernel approach, since it nicely compares to the situation we have with operating systems: There are monolithic kernels like the one of Linux with about 11,430,712 lines of code. And there is a concept called a microkernel, like to one of Minix with about 6,000 lines of executable kernel code. There is still an ongoing discussion which of the two solituons is better. A monolithic kernel is faster, a microkernel has way less critical code (critical code means: a bug there will crash the complete system. If you haven't already, you should read more about mikrokernels on Wikipedia.

However one might think about operating systems - when it comes to Java  I prefer less dependencies and if possible no black magic I don't understand. Especially if this magic involves complex ClassLoader structures. Therefore, here comes Nucleus...

How does this work?

The framework (Nucleus) solves two problems of modular applications:

  • I want provide a service to other components - but I only want to show an interface and they should be provided with my implementation at runtime without knowning(referencing) it.
  • I want to provide a service or callback for other components. I provide an interface, and I want to know all classes implementig it, so I can invoke them.

Ok, we probably need examples for this. Say we want to implement a simple timer service. It provides an interface:

public interface EveryMinute {
    void runTimer() throws Exception;
}

All classes implementing this interface should be invoked every minute. Additionally we provide some infos - namely, when was the timer executed last.

public interface TimerInfo {
    String getLastOneMinuteExecution();
}

Ok, next we need a client for our services:

@Register(classes = EveryMinute.class)
public class ExampleNucleus implements EveryMinute {

    private static Part<TimerInfo> timerInfo = 
                                     Part.of(TimerInfo.class);

    public static void main(String[] args) throws Exception {
        Nucleus.init(); 
        while (true) {
            Thread.sleep(10000);
            System.out.println("Last invocation: "
                    + timerInfo.get().getLastOneMinuteExecution());
        }
    }

    @Override
    public void runTimer() throws Exception {
        System.out.println("The time is: "
                + DateFormat.getTimeInstance().format(new Date()));
    }
}

The static field "Part<TimerInfo> timerInfo" is a simple helper class which fetches the registered instance from Nucleus on the first call and loads it into a private field. So accessing this part has almost no overhead to a normal field access - yet we only reference an interface, not an implementation.

The main method first initializes Nucleus (this performs the classpath scan etc.) and then simply goes into an infinite loop, printing the last execution of our timer every ten seconds.

Since our class wears a @Register annotation, it will be discovered by a special ClassLoadAction (not by Nucleus itself) instantiated and registered for the EveryMinute interface. Its method runTimer will then be invoced by our timer service every minute.

Ok, but how would our TimerService look like?

@Register(classes = { TimerInfo.class })
public class TimerService implements TimerInfo {

    @InjectList(EveryMinute.class)
    private List<EveryMinute> everyMinute;
    private long lastOneMinuteExecution = 0;
    private Timer timer;

    public TimerService() {
        start();
    }

    public void start() {
          timer = new Timer(true);
          // Schedule the task to wait 60 seconds and then invoke
          // every 60 seconds.
          timer.schedule(new InnerTimerTask(),
                      1000 * 60, 
                      1000 * 60);
    }<span face="'Courier New',Courier,monospace" style="">

    private class InnerTimerTask extends TimerTask {

      @Override
        public void run() {
            // Iterate over all instances registered for
            // EveryMinute and invoke its </span><span face="'Courier New',Courier,monospace" style="">runTimer </span><span face="'Courier New',Courier,monospace" style="">method.
            for (EveryMinute task : everyMinute) {
                    task.runTimer();
            }</span>
            // <span face="'Courier New',Courier,monospace" style="">Update lastOneMinuteExecution 
            lastOneMinuteExecution = System.currentTimeMillis();
        }
    }

    @Override
    public String getLastOneMinuteExecution() {
        if (lastOneMinuteExecution == 0) {
            return "-";
        }
        return DateFormat.getDateTimeInstance().format(
                new Date(lastOneMinuteExecution));
    }
}</span>

This class also wears a @Register annotation so that it will also be loaded by the ClassLoadAction named above (the ServiceLoadAction actually). As above it will be instantiated and put into Nucleus (as implementation of TimerInfo). Additionally it wears an @InjectList annotation on the everyMinute field. This will be processed by another class named Factory which performs simple dependency injection. Since its constructur starts a Java Timer for the InnerTimerTask, from that point on all instances registered for EveryMinute will be invoced by this timer - as the name says - every minute.

How is it implemented?

The good thing about Nucleus is, that it is powerful on the one hand, but very simple and small on the other hand. As you could see, there is no inner part for special or privileged services. Everything is built around the kernel - the class Nuclues. Here is what it does:

  • It scans the classpath and looks for files called "component.properties". Those need to be in the root folder of a JAR or in the /src folder of each Eclipse project respectively. 
  • For each identified JAR / project / classpath element, it then collects all contained class files and loads them using Class.forName.
  • For each class, it checks if it implements ClassLoadAction, if yes, it is put into a special list.
  • Each ClassLoadAction is instanciated and each previously seen class is sent to it using: void handle(Class<?> clazz)
  • Finally each ClassLoadAction is notified, that nucleus is complete so that final steps (like annotation based dependency injection) could be performed.

That's it. The only other thing Nucleus provides is a registry which can be used to register and retrieve objects for a class. (An in-depth description of the process above, can be found here: http://andreas.haufler.info/2012/01/iterating-over-all-classes-with.html).

Now to make this framework useable as shown above, there is a set of classes around Nucleus. Most important is the class ServiceLoadAction, which will instantiate each class which wears a @Register annoation, runs Factory.inject (our mini DI tool) on it, and throws it into Nucleus for the listed classes. Whats important: The ServiceLoadActions has no specific rights or privileges, you can easily write your implementation which does smarter stuff.

Next to some annotations, there are three other handy classes when it comes to retrieving instances from Nucleus: Factory, Part and Parts. As noted above, the Factory is a simple dependency injector. Currently only the ServiceLoadAction autmatically uses the Factory, as all classes wearing the @Register annotation are scanned for required injections. You can however use this factory to run injections on your own classes or other ClassLoadActions to do the same as ServiceLoadAction. If you can't or don't want to rely in annotation based dependency magic, you can use the two helper classes Part and Parts. Those are used like normal fields (see ExampleNucleus.timerInfo above) and fetch the appropriate object or list of objects automatically. Since the result is cached, repeated invocations have almost no overhead compared to a normal field.

Nucleus and the example shown above is open source (MIT-License) and available here:
https://github.com/andyHa/scireumOpen/blob/master/src/examples/ExampleNucleus.java
https://github.com/andyHa/scireumOpen/tree/master/src/com/scireum/open/nucleus


If you're interested in using Nucleus, I could put the relevant souces into a separater repository and also provide a release jar - just write a comment below an let me know.


This post is the fourth part of the my series "Enterprisy Java" - We share our hints and tricks how to overcome the obstacles when trying to build several multi tenant web applications out of a set of common modules.
Java (programming language) app

Opinions expressed by DZone contributors are their own.

Related

  • How To Connect a Heroku Java App to a Cloud-Native Database
  • How To Build a Multi-Zone Java App in Days With Vaadin, YugabyteDB, and Heroku
  • How Java Apps Litter Beyond the Heap
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!