Java 9 brings modules as a core concept to the platform, but it’s more than just a language feature. With modules in Java 9, we can improve the design of code to increase maintainability and extensibility. As with every design principle, modularity requires thought and trade-offs to really reap the benefits. This session covers design practices for making codebases more maintainable and extensible. You will also find out about trade-offs to help you make the best choices. Topics include hiding implementations, using services for extensibility, API modules, avoiding cycles, optional dependencies, and dynamically loading modules. Familiarity with modules is helpful but not required. The speakers are the authors of Java 9 Modularity (O’Reilly).
Also see https://javamodularity.com
3. Designing for Modularity with Java 9
What if we ...
... forget about the classpath
... embrace modules
... want to create truly modular software?
4. Designing for Modularity with Java 9
What if we ...
... forget about the classpath
... embrace modules
... want to create truly modular software?
Design Constraints Patterns
5. A Module Primer
module easytext.cli {
requires easytext.analysis;
}
module easytext.analysis {
exports analysis.api;
opens impl;
}
easytext.cli easytext.analysis
other
analysis.api
impl
reflection only!
6. A Module Primer
module easytext.cli {
requires easytext.analysis;
}
module easytext.analysis {
exports analysis.api;
opens impl;
}
Modules define dependencies
explicitly
easytext.cli easytext.analysis
other
analysis.api
impl
reflection only!
7. module easytext.cli {
requires easytext.analysis;
}
A Module Primer
module easytext.analysis {
exports analysis.api;
opens impl;
}
Packages are encapsulated by
default
easytext.cli easytext.analysis
other
analysis.api
impl
reflection only!
8. module easytext.cli {
requires easytext.analysis;
}
A Module Primer
module easytext.analysis {
exports analysis.api;
opens impl;
}
Packages can be “opened” for
deep reflection at run-time
easytext.cli easytext.analysis
other
analysis.api
impl
reflection only!
13. Encapsulation vs. decoupling
Analyzer i = new KincaidAnalyzer();
‣ Even with interfaces, an instance has to be
created…
‣ We need to export our implementation! :-(
module easytext.kincaid {
exports easytext.analysis.kincaid;
}
14. Reflection isn’t a workaround
Class myImpl = Class.forName(…);
MyInterface i = myImpl.newInstance();
‣ Package needs to be open
module easytext.kincaid {
opens easytext.analysis.kincaid;
}
15. Services to the rescue
easytext.gui
easytext.analysis.kincaid easytext.analysis.coleman
easytext.cli
Module System
uses uses
providesprovides
16. Providing a service
module easytext.analyzer.kincaid {
requires easytext.analysis.api;
provides javamodularity.easytext.analysis.api.Analyzer
with javamodularity.easytext.analysis.kincaid.KincaidAnalyzer;
}
17. Implementing a service
public class KincaidAnalyzer implements Analyzer {
public KincaidAnalyzer() { }
@Override
public double analyze(List<List<String>> sentences) {
…
}
}
‣ Just a plain Java class
‣ Not exported!
18. Consuming a service
module easytext.cli {
requires easytext.analysis.api;
uses javamodularity.easytext.analysis.api.Analyzer;
}
19. Consuming a service
Iterable<Analyzer> analyzers = ServiceLoader.load(Analyzer.class);
for (Analyzer analyzer: analyzers) {
System.out.println(
analyzer.getName() + ": " + analyzer.analyze(sentences));
}
module easytext.cli {
requires easytext.analysis.api;
uses javamodularity.easytext.analysis.api.Analyzer;
}
20. Finding the right service
ServiceLoader<Analyzer> analyzers =
ServiceLoader.load(Analyzer.class);
analyzers.stream()
.filter(provider -> …)
.map(ServiceLoader.Provider::get)
.forEach(analyzer -> System.out.println(analyzer.getName()));
‣ ServiceLoader supports streams
‣ Lazy instantiation of services
23. Providing a Guice service
public class ColemanModule extends AbstractModule {
@Override
protected void configure() {
Multibinder
.newSetBinder(binder(), Analyzer.class)
.addBinding().to(ColemanAnalyzer.class);
}
‣ Provider module should export a Guice Module
24. Providing a Guice service
‣ Consumers must be able to compile against the
Guice module
‣ Service impl package must be open
module easytext.algorithm.coleman {
requires easytext.algorithm.api;
requires guice;
requires guice.multibindings;
exports javamodularity.easytext.algorithm.coleman.guice;
opens javamodularity.easytext.algorithm.coleman;
}
javamodularity
│ └── easytext
│ └── algorithm
│ └── coleman
│ ├── ColemanAnalyzer.java
│ └── guice
│ └── ColemanModule.java
└── module-info.java
25. Consuming a Guice service
public class Main {
public static void main(String... args) {
Injector injector =
Guice.createInjector(
new ColemanModule(),
new KincaidModule());
CLI cli = injector.getInstance(CLI.class);
cli.analyze(args[0]);
}
}
26. Consuming a Guice service
‣ Require the implementation Guice modules
‣ Open package for Guice injection
module easytext.cli {
requires easytext.algorithm.api;
requires guice;
requires easytext.algorithm.coleman;
requires easytext.algorithm.kincaid;
opens javamodularity.easytext.cli;
}
27. Consuming a Guice service
‣ Now we can @Inject
public class CLI {
private final Set<Analyzer> analyzers;
@Inject
public CLI(Set<Analyzer> analyzers) {
this.analyzers = analyzers;
}
…
32. Services vs Guice
‣ @Inject vs using an API
‣ Developers might be more familiar the model
‣ Requires more coupling
‣ Adding an implementation requires code changes
Benefits of using Guice
Downsides of using Guice
39. Naming Modules
... is hard
Application modules
‣ Short & memorable
‣ <application>.<component>
‣ e.g. easytext.gui
40. Naming Modules
... is hard
Application modules
‣ Short & memorable
‣ <application>.<component>
‣ e.g. easytext.gui
vs. Library modules
‣ Uniqueness
‣ Reverse DNS
‣ com.mycompany.ourlib
‣ 'Root package'
42. API Modules
‣ Module with exported API only
‣ When multiple implementations are expected
‣ Can also contain 'default' implementation
43. API Modules
‣ Module with exported API only
‣ When multiple implementations are expected
‣ Can also contain 'default' implementation
‣ API modules export:
‣ Interfaces
‣ (Abstract) classes
‣ Exceptions
‣ Etc.
59. Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.name = name;
}
// ..
}
60. Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.name = name;
}
// ..
}
public class Book {
public Author author;
public String title;
public void printBook() {
System.out.printf(
"%s, by %snn%s",
title, author.getName(),
text);
}
61. Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.name = name;
}
// ..
}
public class Book {
public Author author;
public String title;
public void printBook() {
System.out.printf(
"%s, by %snn%s",
title, author.getName(),
text);
}
62. Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.name = name;
}
// ..
}
public class Book {
public Author author;
public String title;
public void printBook() {
System.out.printf(
"%s, by %snn%s",
title, author.getName(),
text);
}
65. Breaking cycles: abstraction
public class Author
implements Named {
// ..
public String getName() {
return this.name;
}
}
public interface Named {
String getName();
}
66. Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Use it if available at run-time
‣ Otherwise, run without
67. Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Use it if available at run-time
‣ Otherwise, run without
Compile-time only dependencies:
module framework {
requires static fastjsonlib;
}
68. Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Use it if available at run-time
‣ Otherwise, run without
Compile-time only dependencies:
module framework {
requires static fastjsonlib;
}
Resolve fastjsonlib if available at run-time, ignore if not