Developing Useful APIs
Transcript of Developing Useful APIs
Developing Useful APIsDmitry Buzdin
October 2013, Riga
What is API?
API is the code you use every day
How many Jars do you have in your classpath?
from 20 to100 jars?
Each library has its own API
!and personality
Moreover, you create new reusable APIs inside your project
may be even in a separate module
APIs are written by developers for
developers
Everything is Open Source today!
Frameworks are turning into micro-
frameworks
I have to learn 100
APIs? !
Is it possible at all?
Option I: lock yourself in a
dungeon
The best code in the world is the one I wrote yesterday!
Option II: learn to ride APIs
Are all APIs different?
Let’s try to understand...
What is a Good API
• Easy to read
• Easy to use
• Hard to misuse
• Easy to extend
How to Achieve That?
• Lets take a look at some:
• module/package level approaches
• class level patterns
• method/code level idioms
Imagine building API for the next 10 years and
1000s of people
Module Level
Dependency Management
• Put less transitive dependencies
• Do you really need that commons-lang?
• Do you remember about Guava incompatibilities
Extreme example: OrientDB - zero dependencies!
Packaging
com.acme.lib
PublicAPI.java
impl
PublicInterface.java
spi CustomListener.java
Internal.java
Util.java
PublicBean.java
Stuff you want to be
reused
Extension API
Class Level
Lets build an API for a reusable gadget
Gadget gadget = new Gadget(name, id, options);!// Gadget is connected at this point
public Gadget(name, id, options) {! this.name = name;! this.id = id;! this.options = options;! connect(); // could throw an exception!}
Gadgets should always be connected
Can not create an instance for testing
Create Gadget by static method or factory class
public Gadget(name, id, options) {! this.name = name;! this.id = id;! this.options = options; !}!!public static Gadget newGadget(name, id, options) {! Gadget gadget = new Gadget(name, id, options);! gadget.connect();! return gadget;!}!!public class GadgetFactory {! public Gadget newGadget(name, id, options) {! Gadget gadget = new Gadget(name, id, options);! gadget.connect();! return gadget;! } !}
public static Gadget newGadget(name, id, options) {! Gadget gadget = new DefaultGadget(name, id, options);! gadget.connect();! return gadget;!}!!public interface Gadget {! void connect();!}!!public class DefaultGadget implements Gadget {! public void connect() {! }!}
Hide your gadget behind interface
Because you could change the implementation Details are well hidden
public static Gadget newGadget(name, id, options) {! Gadget gadget = new DefaultGadget(name, id, options);! gadget.connect();! return gadget;!}!!public interface Gadget {! void connect();!}!!public final class DefaultGadget implements Gadget {! DefaultGadget() {! }! public void connect() {! }!}
Make it final with package-level constructor
Disallow unsanctioned
modification of your code
Open Closed Principle
"software entities (classes, modules, functions, etc.) !should be open for extension, but closed for modification"
public final class DefaultGadget implements Gadget {! public void setStrategy(BehaviorStrategy s) {! // changes the behavior of this Gadget;! }!}
Allowing sanctioned
modifications
Gadget gadget = Gadgets.newGadget(name, id, options);!// Gadget is connected at this point!!!// Similar APIs!Files.createFile(...); // JDK 7!Lists.newArrayList(...); // Guava
Resulting code
Method Overloading
Gadget gadget = Gadgets.newGadget(name, id, options);!Gadget gadget = Gadgets.newGadget(id, options);!Gadget gadget = Gadgets.newGadget(name, options);!Gadget gadget = Gadgets.newGadget(id, enabled);!Gadget gadget = Gadgets.newGadget(id, name, enabled);!
What if different parameter combinations should be supported?
public class GadgetBuilder() {!! // ...! Long id;! String name;!! GadgetBuilder withId(Long id) {! this.id = id;! return this;! }!! GadgetBuilder withName(String name) {! this.name = name;! return this;! }!! Gadget build() {! Gadget gadget = new GadgetImpl();! gadget.setId(id);! gadget.setName(name);! gadget.setOptions(options);! gadget.setEnabled(enabled)! }!}
Covering all possibilities
// Quartz Trigger Builder Example!trigger = newTrigger()! .withIdentity("trigger3", "group1")! .withSchedule(cronSchedule("0 0/2 8-17 * * ?"))! .forJob("myJob", "group1")! .build();
// Constructing stuff using builder!Gadget gadget = new GadgetBuilder()! .withId(1)! .withName(“ok”)! .withOptions(options)! .build();
Much better now!
Lets build a Gadget Service
Gadget Servicepublic final class GadgetService {!! private static final GadgetService instance = new GadgetService();!! public static void getInstance() {! return instance;! }!! public void saveGadget(Gadget gadget) {! ...! }!}
Static fields may produce memory leaks Difficult to test code using that
Gadget Servicepublic class GadgetServiceImpl implements GadgetService {! ! // To be called by factory method/class! public GadgetServiceImpl() {}!! public void saveGadget(Gadget gadget) {! ...! }!!}!!!public class MyClass {! @Inject! GadgetService service;!}
Everyone is using Dependency Injection now
You do not know which Dependency Injection
framework developers will use!
Spring, Guice, CDI, Dagger, PicoContainer etc.
Abstract DI
public interface BeanRegistry {!! void register(Class<?> type);!! <T> T getBean(Class<T> type);!! <T> Collection<T> getBeans(Class<T> type);!!}
https://github.com/DozerMapper/dozer/blob/master/core/src/main/java/org/dozer/inject/DozerBeanContainer.java
Provide DI Bindings
• Write bindings for other frameworks
• Your beans are accessible
• Ready for the next big thing
public class SpringBeanRegistry implements BeanRegistry {! public SpringBeanRegistry(ApplicationContext context) {! //..! }!}
Make your api Extensible
interface Plugin {! void init(Context context);!}
How to allow people to contribute extensions?
Service Provider Interface
http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
ServiceLoader<Plugin> pluginLoader = ServiceLoader.load(Plugin.class);!for (Plugin plugin : pluginLoader) {! plugin.init(context);!}
META-INF/services/lv.jug.api.Pluginlv.jug.api.plugin1.Plugin lv.jug.api.plugin2.Plugin!lv.jug.api.plugin3.Plugin
class MyExtensionPlugin implements Plugin {! @Override! void init(Context context) {! System.out.println(“Hello”);! }!}
SPI Benefits
• No static initialization
• Automatic lookup in all Jars
• Everything is initialized in one place
Annotation Scanning
• Mark extensions with custom annotations
• Use bytecode scanning library
@Extension
final TypeReporter reporter = new TypeReporter() {!! @Override! public Class<? extends Annotation>[] annotations() {! return new Class[]{Extension.class};! }!! @Override! public void reportTypeAnnotation(Class<? extends Annotation> annotation, String className) {! // do something! }!!};!final AnnotationDetector cf = new AnnotationDetector(reporter);!cf.detect();
@Extension!class MyExtensionPlugin implements Plugin {! @Override! void init(Context context) {! System.out.println(“Hello”);! }!}
Finds all annotated classess
https://github.com/rmuller/infomas-asl
Annotation Benefits
• Instantiating and using via reflection
• Easy API to explain
• Quite fast
Method Level
List<String> myList = new ArrayList<>();!updateList(myList);!return myList;
void updateList(List<String> items) {! for (Iterator<String> iterator=items.iterator(); iterator.hasNext();) {! String item = iterator.next() {! if (item.startsWith(“//”)) {! iterator.remove();! }! }!}
Modifying mutable parameters
List<Item> myList = new ArrayList<>();!List<Item> updatedList = updateItems(myList);!return updatedList;
List<String> updateList(final List<String> items) {! List<String> result = new ArrayList<>();! for (String item : items) {! if (!item.startsWith(“//”)) {! result.add(item);! }! }! return result;!}
Considering all method
arguments as immutable
try {! downloadPhoto(id, path);!} catch(ConnectionNotAvailable | PhotoNotFound e) {! System.out.println(“WAT!?”);!}
void downloadPhoto(String id, Path path) ! throws ConnectionNotAvailable, PhotoNotFound {! ...!}
Have to handle all exceptions
downloadPhoto(id, path);
void downloadPhoto(String id, Path path) {! ...!}
Rely on Runtime Exceptions
void uploadPhoto(byte[] photo, String name, String type) {! ...!}
What if photo is too big? What types are supported? RTFM?
void uploadPhoto(InputStream photo,! String name, ! PhotoType type) {! ...!}!!public enum PhotoType {! JPEG, PNG!}
Pass binaries as InputStreams
Create enum for parameters
PhotoService photoService = ...!photoService.openTransaction();!try {! photoService.uploadPhoto(...);! photoService.changeAttributes(...);!} catch (Exception e) {! photoService.rollbackTransaction();!} finally {! photoService.commitTransaction();!}
Just boring...
PhotoService photoService = ...!photoService.doInTransaction(new Execution() {! public void work(Context context) {! context.uploadPhoto(...);! context.changeAttributes(...);! }!});!!public void doInTransaction(Execution execution) {! Context context = openTransactionContext();! try {! execution.work(context);! } catch (Exception e) {! context.rollback();! } finally {! context.commit();! }!}
Writing it once
PhotoService photoService = ...!photoService.doInTransaction(context -> {! context.uploadPhoto(...);! context.changeAttributes(...);!});
Java 8
public Object someServiceMethod() {! return transactionTemplate.execute(new TransactionCallback() {!! // the code in this method executes in a transactional context! public Object doInTransaction(TransactionStatus status) {! updateOperation1();! return resultOfUpdateOperation2();! }! });! }
Same approach in Spring Framework
I learned something today!
All APIs operate on single set of
rules & patterns
APIs have fashion too
Java API from 1998
Java API from 2013
You have to follow fashion trends to ride
APIs
• Treat all reusable classes you write as API
• Use minimum API principle
• Learn by example from open source projects
Recommended Books