LowerProfile

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Conditional Usage)
(More Granularity)
Line 27: Line 27:
==== More Granularity ====
==== More Granularity ====
 +
 +
On the other hand, there is a [[rationalistic]] solution. Your system is [[modular]], isn't it? Just some of your modules may be too ''heavyweight''. How one defines a ''weight'' of a module? In general it is a size of its [[environment]] - which is usually expressed by the outgoing dependencies the module has. If there is a module depending on [[Swing]], it is more ''heavyweight'' than a module which depends only on {{JDK|java/util|ArrayList}} - because the first one requires whole [[JDK]]8 while the latter can run on [[JDK]]8 ''compact 1'' profile (which is not just a name but also about 30MB of download size).
 +
 +
The classic refactoring is to increase granularity of your modules - e.g. split them into (two) parts. Module A (shows a dialog, asks for a number and shows result) can be refactored into two modules: one [[library]] to compute the factorial and another [[JAR]] to show the dialogs and call into the library to get the right results. Very [[rational]] solution - however it requires thinking.
 +
 +
There is (obviously) more [[clueless]] (and in some form safer way) of refactoring. We can abstract away calls into the UI by using [[APISeam]]. Let's leave the whole code of module A untouched and only instead of call to show the result replace it with:
 +
 +
<source lang="java">
 +
Iterator<ShowUI> it = ServiceLoader.load(ShowUI.class).iterator();
 +
ShowUI ui = it.next();
 +
int n = ui.askForNumber();
 +
int fact = Library.factorial(n);
 +
ui.showResult(fact);
 +
 +
// of course there needs to be
 +
 +
public interface ShowUI {
 +
int askForNumber();
 +
void showResult(int res);
 +
}
 +
</source>
 +
 +
This will create a module A-noUI which is capable to run with a restricted [[environment]]. The [[Swing]] specific part would end up in a ''A-UI'' module which has richer set of dependencies and also registers itself as a provider of ''ShowUI'' interface (via [[Lookup]]-like mechanism). When your application runs in original mode, it will behave the same. When executed in headless mode, one needs to provide different implementation of ''ShowUI'' interface and alter the behavior appropriately.
 +
 +
The whole transformation is so trivial, that there should be an automated refactoring for it. Call it for example ''level up package dependency''!
 +
 +
==== The Doublepack Starter ====
 +
 +
 +
==== Unwanted Modules ====
==== Injecting UI & co. ====
==== Injecting UI & co. ====
==== Fake Emulation Classes ====
==== Fake Emulation Classes ====

Revision as of 11:11, 5 March 2014

Profiles are the best thing in JDK8 (in spite they are not as famous as lambdas). Now when they are here with us, it is essential that we will want our applications, frameworks and libraries to be ready for them. Here is few tricks that will help you refactor your code for JDK8.

Contents

Being Ready for JDK Profiles

What does it mean: to be ready for profiles? Well, first and foremost, it means to be able to run on smaller profile than the whole JDK. In older JDKs, up to JDK7, one always had the whole rt.jar. As such it did not matter whether your code used pieces from Swing (like ChangeListener) - all the classes were always available.

The situation changes with profiles. The smallest JDK8 profile is compact 1 and be sure, it does not contain a single class from Swing (neither JNDI or XML parsers). But as an owner of a library or running system you may be interested in in such small profile. Why? Because then your library can be used on small devices (phones, Raspberry PI) or in a headless environment (on servers). For example Grizzly was not ready to run on compact 2 up until I submitted a patch which made a dependency on JNDI conditional since Grizzly revision 2.3.2.

Lower Your Profile Patterns

The need to run on smaller profiles is clear. Let's look at patterns how to adapt your API to such limited environment.

Conditional Usage

This is the most trivial change one can make. Not really beautiful, but effective. A form of clueless solution that was used in case of Grizzly as well. If there is a call to an API not always available, surround it with a catch statement:

Object ref = null;
try {
  ref = new javax.naming.InitialContext().lookup("x");
} catch (LinkageError err) {
  // well, too small of profile. Give up.
}

Effective, especially if the amount of such calls is not huge (nobody wants to pollute own code base with linkage catch statements). Requires compilation against the whole JDK and as such it is a runtime-only solution and possibly can regress any time (unless one actually runs tests on the small profile). On the other hand, it does not require any changes to build infrastructure or use of JDK8 during the build. In short this is the most cluelessness solution.

More Granularity

On the other hand, there is a rationalistic solution. Your system is modular, isn't it? Just some of your modules may be too heavyweight. How one defines a weight of a module? In general it is a size of its environment - which is usually expressed by the outgoing dependencies the module has. If there is a module depending on Swing, it is more heavyweight than a module which depends only on ArrayList - because the first one requires whole JDK8 while the latter can run on JDK8 compact 1 profile (which is not just a name but also about 30MB of download size).

The classic refactoring is to increase granularity of your modules - e.g. split them into (two) parts. Module A (shows a dialog, asks for a number and shows result) can be refactored into two modules: one library to compute the factorial and another JAR to show the dialogs and call into the library to get the right results. Very rational solution - however it requires thinking.

There is (obviously) more clueless (and in some form safer way) of refactoring. We can abstract away calls into the UI by using APISeam. Let's leave the whole code of module A untouched and only instead of call to show the result replace it with:

Iterator<ShowUI> it = ServiceLoader.load(ShowUI.class).iterator();
    ShowUI ui = it.next();
    int n = ui.askForNumber();
    int fact = Library.factorial(n);
    ui.showResult(fact);
 
    // of course there needs to be
 
    public interface ShowUI {
      int askForNumber();
      void showResult(int res);
    }

This will create a module A-noUI which is capable to run with a restricted environment. The Swing specific part would end up in a A-UI module which has richer set of dependencies and also registers itself as a provider of ShowUI interface (via Lookup-like mechanism). When your application runs in original mode, it will behave the same. When executed in headless mode, one needs to provide different implementation of ShowUI interface and alter the behavior appropriately.

The whole transformation is so trivial, that there should be an automated refactoring for it. Call it for example level up package dependency!

The Doublepack Starter

Unwanted Modules

Injecting UI & co.

Fake Emulation Classes

Personal tools
buy