Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
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 ...
Designing for Modularity with Java 9
What if we ...
... forget about the classpath
... embrace modules
... want to create ...
A Module Primer
module easytext.cli {
requires easytext.analysis;
}
module easytext.analysis {
exports analysis.api;
opens...
A Module Primer
module easytext.cli {
requires easytext.analysis;
}
module easytext.analysis {
exports analysis.api;
opens...
module easytext.cli {
requires easytext.analysis;
}
A Module Primer
module easytext.analysis {
exports analysis.api;
opens...
module easytext.cli {
requires easytext.analysis;
}
A Module Primer
module easytext.analysis {
exports analysis.api;
opens...
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…
‣ ...
Reflection isn’t a workaround
Class myImpl = Class.forName(…);
MyInterface i = myImpl.newInstance();
‣ Package needs to be...
Services to the rescue
easytext.gui
easytext.analysis.kincaid easytext.analysis.coleman
easytext.cli
Module System
uses us...
Providing a service
module easytext.analyzer.kincaid {
requires easytext.analysis.api;
provides javamodularity.easytext.an...
Implementing a service
public class KincaidAnalyzer implements Analyzer {
public KincaidAnalyzer() { }
@Override
public do...
Consuming a service
module easytext.cli {
requires easytext.analysis.api;
uses javamodularity.easytext.analysis.api.Analyz...
Consuming a service
Iterable<Analyzer> analyzers = ServiceLoader.load(Analyzer.class);
for (Analyzer analyzer: analyzers) ...
Finding the right service
ServiceLoader<Analyzer> analyzers =
ServiceLoader.load(Analyzer.class);
analyzers.stream()
.filt...
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() {
Multi...
Providing a Guice service
‣ Consumers must be able to compile against the
Guice module
‣ Service impl package must be open...
Consuming a Guice service
public class Main {
public static void main(String... args) {
Injector injector =
Guice.createIn...
Consuming a Guice service
‣ Require the implementation Guice modules
‣ Open package for Guice injection
module easytext.cl...
Consuming a Guice service
‣ Now we can @Inject
public class CLI {
private final Set<Analyzer> analyzers;
@Inject
public CL...
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
‣ Addin...
Services *and* Guice
module easytext.algorithm.coleman {
requires easytext.algorithm.api;
requires guice;
requires guice.m...
Services *and* Guice
module easytext.algorithm.coleman {
requires easytext.algorithm.api;
requires guice;
requires guice.m...
Services *and* Guice
module easytext.cli {
requires easytext.algorithm.api;
requires guice;
requires easytext.algorithm.co...
Services *and* Guice
module easytext.cli {
requires easytext.algorithm.api;
requires guice;
requires easytext.algorithm.co...
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. Lib...
API Modules
API Modules
‣ Module with exported API only
‣ When multiple implementations are expected
‣ Can also contain 'default' impl...
API Modules
‣ Module with exported API only
‣ When multiple implementations are expected
‣ Can also contain 'default' impl...
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 tra...
Small Modules vs. All-in-one
Why choose? Use aggregator modules
module mylib {
requires transitive mylib.one;
requires tra...
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 ...
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.nam...
Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.nam...
Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.nam...
Breaking cycles
public class Author {
private String name;
private List<Book> books;
public Author(String name)
{
this.nam...
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;
}
}
...
Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Us...
Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Us...
Optional dependencies
‣ Requires means compile-time and run-time dependency
‣ What if we want an optional dependency?
‣ Us...
Optional dependencies
Avoid run-time exceptions!
Optional dependencies
try {
Class<?> clazz =
Class.forName("javamodularity.fastjsonlib.FastJson");
FastJson instance =
(Fa...
Optional dependencies
Better yet: use services for optional dependencies
module framework {
requires json.api;
uses json.a...
Layers and
dynamic module
loading
ModuleLayer
mods
├── easytext.algorithm.api
├── easytext.algorithm.coleman
├── easytext.algorithm.kincaid
├── easytext.alg...
ModuleLayer
mods
├── easytext.algorithm.api
├── easytext.algorithm.coleman
├── easytext.algorithm.kincaid
├── easytext.alg...
ModuleLayer
Immutable boot layer
api coleman
kincaid gui
...
One version of a module
No dynamic loading?
App & platform mo...
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 for...
ModuleLayer
boot layer
api
gui
...
layer 1
coleman
parent
layer 2
kincaid
syllable.
counter
parent
Create layers at run-ti...
ModuleLayer Demo
Thank you.
https://javamodularity.com
Paul Bakker
@pbakker
Sander Mak
@Sander_Mak
Upcoming SlideShare
Loading in …5
×

Desiging for Modularity with Java 9

0 views

Published on

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

Published in: Technology

Desiging for Modularity with Java 9

  1. 1. Designing for Modularity Paul Bakker @pbakker Sander Mak @Sander_Mak with Java 9
  2. 2. Today's journey Module primer Services & DI Modular design Layers & loading
  3. 3. Designing for Modularity with Java 9 What if we ... ... forget about the classpath ... embrace modules ... want to create truly modular software?
  4. 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. 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. 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. 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. 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!
  9. 9. Services
  10. 10. EasyText example easytext.gui easytext.analysis.kincaid easytext.analysis.coleman easytext.cli analysis.api
  11. 11. EasyText example easytext.gui easytext.analysis.kincaid easytext.analysis.coleman easytext.cli analysis.api
  12. 12. Encapsulation vs. decoupling Analyzer i = new KincaidAnalyzer(); ‣ Even with interfaces, an instance has to be created…
  13. 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. 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. 15. Services to the rescue easytext.gui easytext.analysis.kincaid easytext.analysis.coleman easytext.cli Module System uses uses providesprovides
  16. 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. 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. 18. Consuming a service module easytext.cli { requires easytext.analysis.api; uses javamodularity.easytext.analysis.api.Analyzer; }
  19. 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. 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
  21. 21. Add new analyses by adding provider modules on the module path
  22. 22. Dependency Injection
  23. 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. 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. 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. 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. 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; } …
  28. 28. Injecting a service algorithm.coleman guice Create binding
  29. 29. Injecting a service cli Requires Guice module algorithm.coleman guice Create binding
  30. 30. Injecting a service cli Requires Guice module algorithm.coleman guice Create binding Create injector
  31. 31. Injecting a service cli Requires Guice module algorithm.coleman guice Create binding Inject instance Create injector
  32. 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. 33. 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; }
  34. 34. 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
  35. 35. Services *and* Guice module easytext.cli { requires easytext.algorithm.api; requires guice; requires easytext.algorithm.coleman; requires easytext.algorithm.kincaid; opens javamodularity.easytext.cli; }
  36. 36. 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
  37. 37. Modular Design
  38. 38. Naming Modules ... is hard
  39. 39. Naming Modules ... is hard Application modules ‣ Short & memorable ‣ <application>.<component> ‣ e.g. easytext.gui
  40. 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'
  41. 41. API Modules
  42. 42. API Modules ‣ Module with exported API only ‣ When multiple implementations are expected ‣ Can also contain 'default' implementation
  43. 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.
  44. 44. Small Modules vs. All-in-one
  45. 45. Small Modules vs. All-in-one ‣ Composability ‣ Reusability Small modules
  46. 46. Small Modules vs. All-in-one ‣ Composability ‣ Reusability Small modules ‣ Ease-of-use All-in-one modules ? ? ? ? ?
  47. 47. Small Modules vs. All-in-one Why choose? Use aggregator modules
  48. 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. 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. 50. Small Modules vs. All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application
  51. 51. Small Modules vs. All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application
  52. 52. Small Modules vs. All-in-one Why choose? Use aggregator modules mylib mylib.one mylib.two mylib.three application Implied readability
  53. 53. Small Modules vs. All-in-one mylib.extra mylib.core
  54. 54. Small Modules vs. All-in-one mylib.extra mylib.core application
  55. 55. Small Modules vs. All-in-one mylib.extra mylib.core application
  56. 56. Small Modules vs. All-in-one mylib.extra mylib.core application
  57. 57. Breaking cycles ‣ Non-modular JARs can have cyclic dependencies ‣ Modules can not
  58. 58. Breaking cycles
  59. 59. Breaking cycles public class Author { private String name; private List<Book> books; public Author(String name) { this.name = name; } // .. }
  60. 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. 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. 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); }
  63. 63. Breaking cycles: abstraction
  64. 64. Breaking cycles: abstraction public class Author implements Named { // .. public String getName() { return this.name; } }
  65. 65. Breaking cycles: abstraction public class Author implements Named { // .. public String getName() { return this.name; } } public interface Named { String getName(); }
  66. 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. 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. 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
  69. 69. Optional dependencies Avoid run-time exceptions!
  70. 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. 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. 72. Layers and dynamic module loading
  73. 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. 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. 75. ModuleLayer Immutable boot layer api coleman kincaid gui ... One version of a module No dynamic loading? App & platform modules
  76. 76. ModuleLayer boot layer api gui ... Create layers at run-time Multiple versions in layers Layers form hierarchy
  77. 77. ModuleLayer boot layer api gui ... layer 1 coleman parent Create layers at run-time Multiple versions in layers Layers form hierarchy
  78. 78. 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
  79. 79. ModuleLayer Demo
  80. 80. Thank you. https://javamodularity.com Paul Bakker @pbakker Sander Mak @Sander_Mak

×