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.

Java APIs - the missing manual

51 views

Published on

This isn’t a talk about microservices, NO-SQL, Container solutions or hip new frameworks. This talk will show some of the standard Java APIs that are part of Java since version 5, 6, 7 or 8. All this features are very helpful to create maintainable and future-proof applications, regardless of whether JavaEE, Spring, JavaFX or any other framework is used. The talk will give an overview of some important standard concepts and APIs of Java like annotations, null values and concurrency. Based on an overview of this topics and some samples the talk will answer questions like:
- How can I create my own annotations?
- How can I create a plugin structure without using frameworks like OSGI?
- What’s the best way to handle NullPointerExceptions?
- How can I write concurrent code that is still maintainable?

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Java APIs - the missing manual

  1. 1. @HendrikEbbers Karakun DevHub_ dev.karakun.com
  2. 2. @HendrikEbbers JavaAPIsThe missing manual
  3. 3. Karakun DevHub_ @HendrikEbbersdev.karakun.com About me • Karakun Co-Founder • Lead of JUG Dortmund • JSR EG member • JavaOne Rockstar, Java Champion • JavaLand Programm Chair
  4. 4. Karakun DevHub_ @HendrikEbbersdev.karakun.com About me
  5. 5. @HendrikEbbers SPI
  6. 6. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Based on Interfaces it's quite easy to define a general service and several implementations in Java
  7. 7. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface public interface MathOperation { String getSign(); double calc(double valA, double valB); }
  8. 8. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • We can provide several implementations of math operations •AddOperation •SubtractOperation
  9. 9. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface public class Multiply implements MathOperation { public String getSign() { return "*";} double calc(double valA, double valB){ return valA * valB; } }
  10. 10. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Instances of the implementations can be accessed by using the general interface • All instances can simply be stored in a collection
  11. 11. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface List<MathOperation> operations = new ArrayList<>(); operations.add(new MultipyOperation()); operations.add(new AddOperation()); operations.add(new DivideOperation()); operations.add(new SubtractOperation()); operations.forEach(o -> { System.out.println("Supported Operation: " + o.getSign()); });
  12. 12. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Let's pimp our interface ;) • With Java 8 default methods we can even define default logic for some parts of the service • Use defaults only if it really makes sense!
  13. 13. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface public interface MathOperation { String getSign(); double calc(double valA, double valB); default String getHelp() { return "No help text for sign" + getSign(); } }
  14. 14. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Let's think about a bigger application • Often based on several modules / jars • Several jars contains several service implementations
  15. 15. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Application Basic Math Module Math Def Module Special Math Module contains the interface
  16. 16. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Application Basic Math Module Math Def Module Special Math Module contains the interface contains implementations contains implementations
  17. 17. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Application Basic Math Module Math Def Module Special Math Module contains the interface contains implementations contains implementations use implementations
  18. 18. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Application Basic Math Module Math Def Module Special Math Module depends on depends on depends ondepends on
  19. 19. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Application Basic Math Module Math Def Module Special Math Module depends on depends on depends ondepends on One Million Dollar Question
  20. 20. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Where should we define the following method? public List<MathOperation> getAllImplInClasspath(); ?
  21. 21. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • We can't define it in the "Math Def Module" since this module has no dependency to any implementation • Method would return
 an empty list
  22. 22. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • We can't define it in the "Basic Math" or the "Special Math" since this modules don't know all implementations. • Method would not return 
 all implementations
  23. 23. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Only the "Application" module knows all implementations since it's the only module that depends on all other modules
  24. 24. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Only the "Application" module knows all implementations since it's the only module that depends on all other modules That was easy, bro!
  25. 25. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Let's add some complexity Basic Math Module Math Def 
 Module Special Math Module Algebra Math Module Geometry Math Module
  26. 26. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Basic Math Module Math Def 
 Module Special Math Module Algebra Math Module Geometry Math Module Customer A App Customer B App Customer C App
  27. 27. Karakun DevHub_ @HendrikEbbersdev.karakun.com Headline • We don't want to implement the
 
 method for each application • We need something new that loads all implementations dynamically public List<MathOperation> getAllImplInClasspath();
  28. 28. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Basic Math Module Math Def 
 Module Special Math Module Algebra Math Module Geometry Math Module Customer A App Customer B App Customer C App
  29. 29. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface Basic Math Module Math Def 
 Module Special Math Module Algebra Math Module Geometry Math Module Customer A App Customer B App Customer C App
  30. 30. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Service Provider Interface (SPI) provides a simple API to solve this issue • Could be used to create a minimal but powerful Plug In API
  31. 31. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Start by creating an interface public interface MathOperation { String getSign(); double calc(double valA, double valB); } we already have it
  32. 32. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Each module that provides implementations of the interface most provide a service file • File must be located under META-INF/services/ before Java 9
  33. 33. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Name of the file must be equals to the interface name META-INF/services/com.canoo.math.MathOperation File name before Java 9
  34. 34. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • File simply contains names of all implementations com.canoo.basic.AddOperation
 com.canoo.basic.MinusOperation define one implementation per line before Java 9
  35. 35. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • The Service Provider Interface (SPI) will automatically find all implementations at runtime • Instances of the classes will automatically be created • Default constructor is needed
  36. 36. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • You can simply iterate over all implementations Iterator<MathOperation> iterator = ServiceLoader .load(MathOperation.class) .iterator(); 
 while (iterator.hasNext()) {
 MathOperation operation = iterator.next();
 }
  37. 37. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • At the end we can implement the
 
 method simple in the "Math Def Module" • The service provider 
 will automatically find
 all implementations public List<MathOperation> getAllImplInClasspath();
  38. 38. @HendrikEbbers Annotations
  39. 39. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Let's start with a quiz :) • How many Java annotations are used in the code? ?
  40. 40. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } }
  41. 41. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } }
  42. 42. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } } @Deprecated annotation is defined in the JRE
  43. 43. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } } the @Important annotation is not defined in the JRE but it's easy to create your own custom annotations (like @Autowired in Spring)
  44. 44. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } } the @Static annotation is configured by a boolean parameter
  45. 45. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations /**
 * A helper class
 * @see Class#getModifiers()
 *
 * @author Hendrik Ebbers * @deprecated
 */ @Deprecated public class Helper { @Important private Logger logger = new Logger(); /**
 * Return {@code true} if the integer argument includes the {@code private} modifier, {@code false} otherwise.
 *
 * @param mod a set of modifiers
 * @return {@code true} if {@code mod} includes the {@code private} modifier; {@code false} otherwise.
 */ @Static(false)
 public static boolean isPrivate(@Param int mod) {
 return (mod & 0x00000002) != 0;
 } } the @Param annotation is another custom annotation that is used to annotate a method param
  46. 46. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Annotations can be used to add metadata to the code • Annotations can be added to different points of the code • Annotation can be parametrable • Custom annotations can be defined
  47. 47. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations public @interface MyAnnotation { }
  48. 48. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations public @interface MyAnnotation { } access modifier name annotation keyword
  49. 49. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations @Documented @Retention(RUNTIME) @Target(METHOD) public @interface MyAnnotation { }
  50. 50. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations ? @Documented @Retention(RUNTIME) @Target(METHOD) public @interface MyAnnotation { }
  51. 51. Karakun DevHub_ @HendrikEbbersdev.karakun.com @Documented @Retention(RUNTIME) @Target(ANNOTATION_TYPE) public @interface Documented { } Annotations
  52. 52. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • If an annotation is annotated with @Documented the usage of the annotation will be shown in JavaDoc • The usage of @Documented has no effect at runtime
  53. 53. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • The @Target annotation defines where an annotation can be used. • The enum ElementType defines all supported types
  54. 54. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • TYPE • FIELD • METHOD • PARAMETER • CONSTRUCTOR • LOCAL_VARIABLE • ANNOTATION_TYPE • PACKAGE • TYPE_PARAMETER • TYPE_USE[ [
  55. 55. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations @Target(METHOD) public @interface MyAnnotation { } @MyAnnotation public void sqrt(int value) { return 1; }
  56. 56. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations @Target(METHOD) public @interface MyAnnotation { } public void sqrt(@MyAnnotation int value) { return 1; }
  57. 57. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations @Target(PARAMETER) public @interface MyAnnotation { } public void sqrt(@MyAnnotation int value) { return 1; }
  58. 58. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations @Target({PARAMETER, METHOD}) public @interface MyAnnotation { } @MyAnnotation public void sqrt(@MyAnnotation int value) { return 1; }
  59. 59. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • The @Retention annotation defines how long an annotation will be retained. • The enum RetentionPolicy defines all supported types
  60. 60. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations SOURCE CLASS RUNTIME remove annotation at compiletime remove annotation at runtime do not remove annotation
  61. 61. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations SOURCE CLASS RUNTIME general meta information like @Override for example the annotations of findbugs All annotations that are used at runtime
  62. 62. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • There are several ways how annotations can be used • Annotations like @Deprecated define useful information for the developer (IDE support) and compiler
  63. 63. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Projects like Lombok use annotations for annotation processing at compile time • Annotations like @Inject are used to modify objects at runtime
  64. 64. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • All annotations that have a @Retention policy that is defined as RUNTIME can be accessed by using reflection. • In general this is one of the tricks that make DI or Spring work
  65. 65. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • How can I check for annotations in my code at runtime ?
  66. 66. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Only the "Application" module knows all implementations since it's the only module that depends on all other modules Reflections
  67. 67. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations Class cls = MyCustomClass.class;
 boolean val = cls.isAnnotationPresent(MyAnnotation.class); • We will see more samples later
  68. 68. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • But there is more • Annotations can be "configured" • Java provides some nice language features here
  69. 69. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Annotations can contain parameters public @interface Column { String name(); } @Column(name="Description") private String desc;
  70. 70. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Annotations can define multiple parameters • The following types are allowed: • primitive • String • Class • an Enum • another Annotation • an array of any of the types
  71. 71. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • If an Annotations only needs one param it should be named as "value" • By doing so the name must not be specified when using the annotation
  72. 72. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations public @interface Column { String value(); } @Column("Name") private String name;
  73. 73. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Annotations can define a default value for a parameters public @interface Column { String name() default ""; } @Column private String name;
  74. 74. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Access annotation parameters final Class cls = MyCustomClass.class; 
 if(cls.isAnnotationPresent(MyAnnotation.class)){ final MyAnnotation a = cls.getAnnotation(MyAnnotation.class); final String name = a.name(); System.out.println("Name: " + name); }
  75. 75. Karakun DevHub_ @HendrikEbbersdev.karakun.com Annotations • Additional topics: •@Inherited • Lists of annotations • Meta annotations • Annotation Processor
  76. 76. @HendrikEbbers Stream.error
  77. 77. Karakun DevHub_ @HendrikEbbersdev.karakun.com Streams • Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections. Stream.of("A", "B", "C") .forEach(s -> System.out.println(s));
  78. 78. Karakun DevHub_ @HendrikEbbersdev.karakun.com Streams • Sequence of elements • Aggregate operations (filter, map, limit, reduce…) • Pipelining − Most of the stream operations return stream itself Filter Map Reduce
  79. 79. Karakun DevHub_ @HendrikEbbersdev.karakun.com Streams • Let's create a stream that converts String values to int values
  80. 80. Karakun DevHub_ @HendrikEbbersdev.karakun.com Streams Stream.of("1", "2", "3") .map(s -> Integer.parseInt(s)) .forEach(i -> System.out.println("INT " + i));
  81. 81. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Only the "Application" module knows all implementations since it's the only module that depends on all other modules That was easy, bro!
  82. 82. Karakun DevHub_ @HendrikEbbersdev.karakun.com Streams • Let's see if this is really the perfect solution… Stream.of("1", "2", "3", "X") .map(s -> Integer.parseInt(s)) .forEach(i -> System.out.println("INT " + i));
  83. 83. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • Stream operations are not made to handle exceptions • To be more clear the functional interfaces in java.util.function can not handle exceptions.
  84. 84. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • Let's find a solution Stream.of("1", "2", "3", "X") .map(s -> { try { return Integer.parseInt(s) } catch(Exception e) { return -1; } }) .forEach(i -> System.out.println("INT " + i));
  85. 85. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • Let's find a solution Stream.of("1", "2", "3", "X") .map(s -> { try { return Integer.parseInt(s) } catch(Exception e) { return -1; } }) .forEach(i -> System.out.println("INT " + i)); Nope! That is not what we want…
  86. 86. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • Let's find a solution Stream.of("1", "2", "3", "X") .map(s -> { try { return Integer.parseInt(s) } catch(Exception e) { throw new RuntimeException("…",e); } }) .forEach(i -> System.out.println("INT " + i));
  87. 87. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • Let's find a solution Stream.of("1", "2", "3", "X") .map(s -> { try { return Integer.parseInt(s) } catch(Exception e) { throw new RuntimeException("…",e); } }) .forEach(i -> System.out.println("INT " + i));
  88. 88. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams public int convert(String s) { try { return Integer.parseInt(s) } catch(Exception e) { throw new RuntimeException("Can not convert to int!"e); } } Stream.of("1", "2", "3", "X") .map(s -> convert(s)) .forEach(i -> System.out.println("INT " + i));
  89. 89. Karakun DevHub_ @HendrikEbbersdev.karakun.com Exceptions in Streams • The current approach looks ok but is far away from a general solution • We need to be more generic
  90. 90. Karakun DevHub_ @HendrikEbbersdev.karakun.com Checked functions • Let's create or custom functional interface • Libraries like VAVR do exactly this @FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws Exception; }
  91. 91. Karakun DevHub_ @HendrikEbbersdev.karakun.com Checked functions @FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws Exception; static <T,R> Function<T,R> wrap(CheckedFunction<T,R> f) { Objects.requireNonNull(f, "Function must not be null"); return t -> { try { f.apply(t); } catch(Exception e) { throw new RuntimeException(e); } }; } }
  92. 92. Karakun DevHub_ @HendrikEbbersdev.karakun.com Checked functions • Now we have a general solution to handle exceptions in streams… Stream.of("1", "2", "3", "X") .map(s -> CheckedFunction.wrap(s -> Integer.parseInt(s))) .forEach(i -> System.out.println("INT " + i));
  93. 93. @HendrikEbbers Result 1 2 3 Error
  94. 94. Karakun DevHub_ @HendrikEbbersdev.karakun.com Service Provider Interface • Only the "Application" module knows all implementations since it's the only module that depends on all other modules That was easy, bro!
  95. 95. Karakun DevHub_ @HendrikEbbersdev.karakun.com Checked functions • Let's just change 2 chars… Stream.of("1", "X", "3", "4") .map(s -> CheckedFunction.wrap(s -> Integer.parseInt(s))) .forEach(i -> System.out.println("INT " + i));
  96. 96. @HendrikEbbers Result 1
  97. 97. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types • The exception stops the stream • But we can not return a default value • We need to find a way to handle the complete stream with exception support • Again libraries like VAVR have some solutions
  98. 98. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types Stream.of("1", "2", "A", "4").map(Result.of(s ->Integer.parseInt(s))) .forEach(r -> { if(r.isSuccessfull()) { System.out.println(r.getInput() + " -> " + r.getResult()); } else { System.out.println("Error for " + r.getInput()); Integer.parseInt(""); } });
  99. 99. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types public void handle(final Result<String, Integer> result) { if(result.isSuccessfull()) { System.out.println(result.getInput() + " -> " + result.getResult()); } else { System.out.println("Error for " + result.getInput()); } } Stream.of("1", "2", "A", "4") .map(Result.of(s ->Integer.parseInt(s))) .forEach(r -> handle(r));
  100. 100. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types Result<INPUT, OUTPUT> Sucess<INPUT, OUTPUT> Fail<INPUT, OUTPUT> Extends Extends
  101. 101. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types Input Result
  102. 102. Karakun DevHub_ @HendrikEbbersdev.karakun.com Additonal (functional) types Input Exeption
  103. 103. @HendrikEbbers Karakun AG @ • Web-APIs: Das ultimative Handbuch • Progressive Web Apps mit der Service Worker API • Free React nighthacking in the office space of Eppleton https://goo.gl/1wX3t3 dev.karakun.com | @HendrikEbbers

×