Designing for Modularity
Paul Bakker
@pbakker
Sander Mak
@Sander_Mak
with Java 9
Today's journey
Module primer
Services & DI
Modular design
Layers & loading
Designing for Modularity with Java 9
What if we ...
... forget about the classpath
... embrace modules
... want to create truly modular software?
Designing for Modularity with Java 9
What if we ...
... forget about the classpath
... embrace modules
... want to create truly modular software?
Design Constraints Patterns
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!
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!
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!
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!
Services
EasyText example
easytext.gui
easytext.analysis.kincaid easytext.analysis.coleman
easytext.cli
analysis.api
EasyText example
easytext.gui
easytext.analysis.kincaid easytext.analysis.coleman
easytext.cli
analysis.api
Encapsulation vs. decoupling
Analyzer i = new KincaidAnalyzer();
‣ Even with interfaces, an instance has to be
created…
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;
}
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;
}
Services to the rescue
easytext.gui
easytext.analysis.kincaid easytext.analysis.coleman
easytext.cli
Module System
uses uses
providesprovides
Providing a service
module easytext.analyzer.kincaid {
requires easytext.analysis.api;
provides javamodularity.easytext.analysis.api.Analyzer
with javamodularity.easytext.analysis.kincaid.KincaidAnalyzer;
}
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!
Consuming a service
module easytext.cli {
requires easytext.analysis.api;
uses javamodularity.easytext.analysis.api.Analyzer;
}
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;
}
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
Add new
analyses by
adding provider
modules on the
module path
Dependency Injection
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
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
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]);
}
}
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;
}
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;
}
…
Injecting a service
algorithm.coleman
guice
Create binding
Injecting a service
cli
Requires Guice module
algorithm.coleman
guice
Create binding
Injecting a service
cli
Requires Guice module
algorithm.coleman
guice
Create binding
Create injector
Injecting a service
cli
Requires Guice module
algorithm.coleman
guice
Create binding Inject instance
Create injector
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
Services *and* Guice
module easytext.algorithm.coleman {
requires easytext.algorithm.api;
requires guice;
requires guice.multibindings;
exports javamodularity.easytext.algorithm.coleman.guice;
opens javamodularity.easytext.algorithm.coleman;
}
Services *and* Guice
module easytext.algorithm.coleman {
requires easytext.algorithm.api;
requires guice;
requires guice.multibindings;
exports javamodularity.easytext.algorithm.coleman.guice;
opens javamodularity.easytext.algorithm.coleman;
}
provides com.google.inject.AbstractModule
with javamodularity.easytext.algorithm.coleman.guice.ColemanModule
Services *and* Guice
module easytext.cli {
requires easytext.algorithm.api;
requires guice;
requires easytext.algorithm.coleman;
requires easytext.algorithm.kincaid;
opens javamodularity.easytext.cli;
}
Services *and* Guice
module easytext.cli {
requires easytext.algorithm.api;
requires guice;
requires easytext.algorithm.coleman;
requires easytext.algorithm.kincaid;
opens javamodularity.easytext.cli;
}
uses com.google.inject.AbstractModule
Modular Design
Naming Modules
... is hard
Naming Modules
... is hard
Application modules
‣ Short & memorable
‣ <application>.<component>
‣ e.g. easytext.gui
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'
API Modules
API Modules
‣ Module with exported API only
‣ When multiple implementations are expected
‣ Can also contain 'default' implementation
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.
Small Modules vs. All-in-one
Small Modules vs. All-in-one
‣ Composability
‣ Reusability
Small modules
Small Modules vs. All-in-one
‣ Composability
‣ Reusability
Small modules
‣ Ease-of-use
All-in-one modules ?
?
?
?
?
Small Modules vs. All-in-one
Why choose? Use aggregator modules
Small Modules vs. All-in-one
Why choose? Use aggregator modules
module mylib {
requires transitive mylib.one;
requires transitive mylib.two;
requires transitive mylib.three;
}

Module without code, using implied readability:
Small Modules vs. All-in-one
Why choose? Use aggregator modules
module mylib {
requires transitive mylib.one;
requires transitive mylib.two;
requires transitive mylib.three;
}

Module without code, using implied readability: mylib
mylib.one
mylib.two
mylib.three
Small Modules vs. All-in-one
Why choose? Use aggregator modules
mylib
mylib.one
mylib.two
mylib.three
application
Small Modules vs. All-in-one
Why choose? Use aggregator modules
mylib
mylib.one
mylib.two
mylib.three
application
Small Modules vs. All-in-one
Why choose? Use aggregator modules
mylib
mylib.one
mylib.two
mylib.three
application
Implied readability
Small Modules vs. All-in-one
mylib.extra
mylib.core
Small Modules vs. All-in-one
mylib.extra
mylib.core
application
Small Modules vs. All-in-one
mylib.extra
mylib.core
application
Small Modules vs. All-in-one
mylib.extra
mylib.core
application
Breaking cycles
‣ Non-modular JARs can have cyclic dependencies
‣ Modules can not
Breaking cycles
Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.name = name;
}
// ..
}
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);
}
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);
}
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);
}
Breaking cycles: abstraction
Breaking cycles: abstraction
public class Author
implements Named {
// ..
public String getName() {
return this.name;
}
}
Breaking cycles: abstraction
public class Author
implements Named {
// ..
public String getName() {
return this.name;
}
}
public interface Named {
String getName();
}
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
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;
}
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
Optional dependencies
Avoid run-time exceptions!
Optional dependencies
try {
Class<?> clazz =
Class.forName("javamodularity.fastjsonlib.FastJson");
FastJson instance =
(FastJson) clazz.getConstructor().newInstance();
System.out.println("Using FastJson");
} catch (ReflectiveOperationException e) {
System.out.println("Oops, we need a fallback!");
}
Avoid run-time exceptions!
Optional dependencies
Better yet: use services for optional dependencies
module framework {
requires json.api;
uses json.api.Json;
}
ServiceLoader.load(Json.class)
.stream()
.filter(this::isFast)
.findFirst();
Layers and
dynamic module
loading
ModuleLayer
mods
├── easytext.algorithm.api
├── easytext.algorithm.coleman
├── easytext.algorithm.kincaid
├── easytext.algorithm.naivesyllablecounter
├── easytext.algorithm.nextgensyllablecounter
├── easytext.cli
└── easytext.gui
Module path
ModuleLayer
mods
├── easytext.algorithm.api
├── easytext.algorithm.coleman
├── easytext.algorithm.kincaid
├── easytext.algorithm.naivesyllablecounter
├── easytext.algorithm.nextgensyllablecounter
├── easytext.cli
└── easytext.gui
Module path
boot layer
api coleman
kincaid gui
...
at run-time
ModuleLayer
Immutable boot layer
api coleman
kincaid gui
...
One version of a module
No dynamic loading?
App & platform modules
ModuleLayer
boot layer
api
gui
...
Create layers at run-time
Multiple versions in layers
Layers form hierarchy
ModuleLayer
boot layer
api
gui
...
layer 1
coleman
parent
Create layers at run-time
Multiple versions in layers
Layers form hierarchy
ModuleLayer
boot layer
api
gui
...
layer 1
coleman
parent
layer 2
kincaid
syllable.
counter
parent
Create layers at run-time
Multiple versions in layers
Layers form hierarchy
ModuleLayer Demo
Thank you.
https://javamodularity.com
Paul Bakker
@pbakker
Sander Mak
@Sander_Mak

Desiging for Modularity with Java 9

  • 1.
    Designing for Modularity PaulBakker @pbakker Sander Mak @Sander_Mak with Java 9
  • 2.
    Today's journey Module primer Services& DI Modular design Layers & loading
  • 3.
    Designing for Modularitywith Java 9 What if we ... ... forget about the classpath ... embrace modules ... want to create truly modular software?
  • 4.
    Designing for Modularitywith Java 9 What if we ... ... forget about the classpath ... embrace modules ... want to create truly modular software? Design Constraints Patterns
  • 5.
    A Module Primer moduleeasytext.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 moduleeasytext.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 { requireseasytext.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 { requireseasytext.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!
  • 9.
  • 10.
  • 11.
  • 12.
    Encapsulation vs. decoupling Analyzeri = new KincaidAnalyzer(); ‣ Even with interfaces, an instance has to be created…
  • 13.
    Encapsulation vs. decoupling Analyzeri = 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 aworkaround Class myImpl = Class.forName(…); MyInterface i = myImpl.newInstance(); ‣ Package needs to be open module easytext.kincaid { opens easytext.analysis.kincaid; }
  • 15.
    Services to therescue easytext.gui easytext.analysis.kincaid easytext.analysis.coleman easytext.cli Module System uses uses providesprovides
  • 16.
    Providing a service moduleeasytext.analyzer.kincaid { requires easytext.analysis.api; provides javamodularity.easytext.analysis.api.Analyzer with javamodularity.easytext.analysis.kincaid.KincaidAnalyzer; }
  • 17.
    Implementing a service publicclass KincaidAnalyzer implements Analyzer { public KincaidAnalyzer() { } @Override public double analyze(List<List<String>> sentences) { … } } ‣ Just a plain Java class ‣ Not exported!
  • 18.
    Consuming a service moduleeasytext.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 rightservice 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
  • 21.
    Add new analyses by addingprovider modules on the module path
  • 22.
  • 23.
    Providing a Guiceservice 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 Guiceservice ‣ 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 Guiceservice 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 Guiceservice ‣ 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 Guiceservice ‣ Now we can @Inject public class CLI { private final Set<Analyzer> analyzers; @Inject public CLI(Set<Analyzer> analyzers) { this.analyzers = analyzers; } …
  • 28.
  • 29.
    Injecting a service cli RequiresGuice module algorithm.coleman guice Create binding
  • 30.
    Injecting a service cli RequiresGuice module algorithm.coleman guice Create binding Create injector
  • 31.
    Injecting a service cli RequiresGuice module algorithm.coleman guice Create binding Inject instance Create injector
  • 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
  • 33.
    Services *and* Guice moduleeasytext.algorithm.coleman { requires easytext.algorithm.api; requires guice; requires guice.multibindings; exports javamodularity.easytext.algorithm.coleman.guice; opens javamodularity.easytext.algorithm.coleman; }
  • 34.
    Services *and* Guice moduleeasytext.algorithm.coleman { requires easytext.algorithm.api; requires guice; requires guice.multibindings; exports javamodularity.easytext.algorithm.coleman.guice; opens javamodularity.easytext.algorithm.coleman; } provides com.google.inject.AbstractModule with javamodularity.easytext.algorithm.coleman.guice.ColemanModule
  • 35.
    Services *and* Guice moduleeasytext.cli { requires easytext.algorithm.api; requires guice; requires easytext.algorithm.coleman; requires easytext.algorithm.kincaid; opens javamodularity.easytext.cli; }
  • 36.
    Services *and* Guice moduleeasytext.cli { requires easytext.algorithm.api; requires guice; requires easytext.algorithm.coleman; requires easytext.algorithm.kincaid; opens javamodularity.easytext.cli; } uses com.google.inject.AbstractModule
  • 37.
  • 38.
  • 39.
    Naming Modules ... ishard Application modules ‣ Short & memorable ‣ <application>.<component> ‣ e.g. easytext.gui
  • 40.
    Naming Modules ... ishard Application modules ‣ Short & memorable ‣ <application>.<component> ‣ e.g. easytext.gui vs. Library modules ‣ Uniqueness ‣ Reverse DNS ‣ com.mycompany.ourlib ‣ 'Root package'
  • 41.
  • 42.
    API Modules ‣ Modulewith exported API only ‣ When multiple implementations are expected ‣ Can also contain 'default' implementation
  • 43.
    API Modules ‣ Modulewith exported API only ‣ When multiple implementations are expected ‣ Can also contain 'default' implementation ‣ API modules export: ‣ Interfaces ‣ (Abstract) classes ‣ Exceptions ‣ Etc.
  • 44.
  • 45.
    Small Modules vs.All-in-one ‣ Composability ‣ Reusability Small modules
  • 46.
    Small Modules vs.All-in-one ‣ Composability ‣ Reusability Small modules ‣ Ease-of-use All-in-one modules ? ? ? ? ?
  • 47.
    Small Modules vs.All-in-one Why choose? Use aggregator modules
  • 48.
    Small Modules vs.All-in-one Why choose? Use aggregator modules module mylib { requires transitive mylib.one; requires transitive mylib.two; requires transitive mylib.three; }
 Module without code, using implied readability:
  • 49.
    Small Modules vs.All-in-one Why choose? Use aggregator modules module mylib { requires transitive mylib.one; requires transitive mylib.two; requires transitive mylib.three; }
 Module without code, using implied readability: mylib mylib.one mylib.two mylib.three
  • 50.
    Small Modules vs.All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application
  • 51.
    Small Modules vs.All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application
  • 52.
    Small Modules vs.All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application Implied readability
  • 53.
    Small Modules vs.All-in-one mylib.extra mylib.core
  • 54.
    Small Modules vs.All-in-one mylib.extra mylib.core application
  • 55.
    Small Modules vs.All-in-one mylib.extra mylib.core application
  • 56.
    Small Modules vs.All-in-one mylib.extra mylib.core application
  • 57.
    Breaking cycles ‣ Non-modularJARs can have cyclic dependencies ‣ Modules can not
  • 58.
  • 59.
    Breaking cycles public classAuthor { private String name; private List<Book> books; public Author(String name) { this.name = name; } // .. }
  • 60.
    Breaking cycles public classAuthor { 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 classAuthor { 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 classAuthor { 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); }
  • 63.
  • 64.
    Breaking cycles: abstraction publicclass Author implements Named { // .. public String getName() { return this.name; } }
  • 65.
    Breaking cycles: abstraction publicclass Author implements Named { // .. public String getName() { return this.name; } } public interface Named { String getName(); }
  • 66.
    Optional dependencies ‣ Requiresmeans 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 ‣ Requiresmeans 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 ‣ Requiresmeans 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
  • 69.
  • 70.
    Optional dependencies try { Class<?>clazz = Class.forName("javamodularity.fastjsonlib.FastJson"); FastJson instance = (FastJson) clazz.getConstructor().newInstance(); System.out.println("Using FastJson"); } catch (ReflectiveOperationException e) { System.out.println("Oops, we need a fallback!"); } Avoid run-time exceptions!
  • 71.
    Optional dependencies Better yet:use services for optional dependencies module framework { requires json.api; uses json.api.Json; } ServiceLoader.load(Json.class) .stream() .filter(this::isFast) .findFirst();
  • 72.
  • 73.
    ModuleLayer mods ├── easytext.algorithm.api ├── easytext.algorithm.coleman ├──easytext.algorithm.kincaid ├── easytext.algorithm.naivesyllablecounter ├── easytext.algorithm.nextgensyllablecounter ├── easytext.cli └── easytext.gui Module path
  • 74.
    ModuleLayer mods ├── easytext.algorithm.api ├── easytext.algorithm.coleman ├──easytext.algorithm.kincaid ├── easytext.algorithm.naivesyllablecounter ├── easytext.algorithm.nextgensyllablecounter ├── easytext.cli └── easytext.gui Module path boot layer api coleman kincaid gui ... at run-time
  • 75.
    ModuleLayer Immutable boot layer apicoleman kincaid gui ... One version of a module No dynamic loading? App & platform modules
  • 76.
    ModuleLayer boot layer api gui ... Create layersat run-time Multiple versions in layers Layers form hierarchy
  • 77.
    ModuleLayer boot layer api gui ... layer 1 coleman parent Createlayers at run-time Multiple versions in layers Layers form hierarchy
  • 78.
    ModuleLayer boot layer api gui ... layer 1 coleman parent layer2 kincaid syllable. counter parent Create layers at run-time Multiple versions in layers Layers form hierarchy
  • 79.
  • 80.