Sei sulla pagina 1di 25

API Design Done Right

Some guidelines which every programmer should probably know

Monday, September 12, 2011

API Design Done WRONG

Monday, September 12, 2011

public int countBigCustomers() {


Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:h2:mem:");
PreparedStatement statement = connection
.prepareStatement("SELECT COUNT(*) FROM CUSTOMERS WHERE REVENUE > ?");
// Do indexes start from 0 or 1?
statement.setLong(1, 1000000L);
ResultSet resultSet = statement.executeQuery();
resultSet.next();
// Do indexes start from 0 or 1?
int result = resultSet.getInt(1);
return result;
} catch (SQLException e) {
// Checked exception - ouch!
throw new RuntimeException(e);
} finally {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
// Checked exception - aargh
throw new RuntimeException(e);
}
}
}

Copyright Reaktor 2011


Monday, September 12, 2011

public int countBigCustomers() {


Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:h2:mem:");
PreparedStatement statement = connection
.prepareStatement("SELECT COUNT(*) FROM CUSTOMERS WHERE REVENUE > ?");
// Do indexes start from 0 or 1?
statement.setLong(1, 1000000L);
ResultSet resultSet = statement.executeQuery();
resultSet.next();
// Do indexes start from 0 or 1?
int result = resultSet.getInt(1);
return result;
} catch (SQLException e) {
// Checked exception - ouch!
throw new RuntimeException(e);
} finally {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
// Checked exception - aargh
throw new RuntimeException(e);
}
}
}

Copyright Reaktor 2011


Monday, September 12, 2011

A bit better way

public int countBigCustomers() {


JdbcTemplate jdbc = new JdbcTemplate(new SingleConnectionDataSource(
"jdbc:h2:mem:", false));
return jdbc.queryForInt(
"SELECT COUNT(*) FROM CUSTOMERS WHERE REVENUE > ?", 1000000L);
}

Copyright Reaktor 2011


Monday, September 12, 2011

Confidential

Why API Design Matters


Every piece of code has an API

Usually write-once,
read/learn many times
by many different people

Copyright Reaktor 2011


Monday, September 12, 2011

Why API Design Matters


Bad API usually leads to bad client code
Bad API design tends to infect all above
layers

Copyright Reaktor 2011


Monday, September 12, 2011

http://www.flickr.com/photos/bamshad/1469713774/sizes/s/in/photostream/

API is a User Interface


The same principles apply

Copyright Reaktor 2011


Monday, September 12, 2011

API is a User Interface

The purpose and usages must be known

Copyright Reaktor 2011


Monday, September 12, 2011

API is a User Interface


Make correct usage easy
Make wrong usage hard
(or impossible)

Copyright Reaktor 2011


Monday, September 12, 2011

Good API
Intuitive
Easy to learn from simple examples
Self explanatory
Effort minimizing

Copyright Reaktor 2011


Monday, September 12, 2011

Levels of abstraction
JDBC API

public int countBigCustomers() {


Connection connection = null;


try {



connection = DriverManager.getConnection
("jdbc:h2:mem:");



PreparedStatement statement = connection





.prepareStatement("SELECT COUNT(*)
FROM CUSTOMERS WHERE REVENUE > ?");



// Do indexes start from 0 or 1?



statement.setLong(1, 1000000L);



ResultSet resultSet = statement.executeQuery();



resultSet.next();



// Do indexes start from 0 or 1?



int result = resultSet.getInt(1);



return result;


} catch (SQLException e) {



// Checked exception - ouch!



throw new RuntimeException(e);


} finally {



try {




if (connection != null && !
connection.isClosed()) {





connection.close();




}



} catch (SQLException e) {




// Checked exception - aargh




throw new RuntimeException(e);



}


}

}

Good abstraction level for lowlevel library code, bad for


application code

Copyright Reaktor 2011


Monday, September 12, 2011

Spring API





public int countBigCustomers() {



JdbcTemplate jdbc = new JdbcTemplate(new SingleConnectionDataSource(



"jdbc:h2:mem:", false));

return jdbc.queryForInt(



"SELECT COUNT(*) FROM CUSTOMERS WHERE REVENUE > ?", 1000000L);
}

Good abstraction level for


application code

Levels of abstraction

Copyright Reaktor 2011


Monday, September 12, 2011

Client perspective with TDD


@RunWith(Enclosed.class)
public class OrderBuilderTest {
public static class WhenBuildingOrderWithTwoProducts {
private Order order;



create




@Before
public void setUp() {
// API as an user interface - how user of the API would
an order
order = new OrderBuilder(CustomerId.FANBOY_CUSTOMER)
.with(ProductId.JPHONE_6, 4)
.with(ProductId.CONSTELLATION_TABLET, 2)
.build();
}

@Test
public void orderHasTwoRows() {
assertThat(order.orderRows().size(), is(2));
}

Copyright Reaktor 2011


Monday, September 12, 2011

Simplicity

Copyright Reaktor 2011


Monday, September 12, 2011

Object initialization
An object should be in valid, usable state
immediately.





Component c = new Component();


// Here the component cannot be used
// yet, since it is not initialized,
// i.e. it is in an invalid state.
c.initialize(new Configuration());
// Only now the component can be used.
Component c = new Component(new
Configuration());
// Now the component can be used
// immediately.

Copyright Reaktor 2011


Monday, September 12, 2011

BAD!!!
BETTER

Object initialization
Use builders or factories for more complex
initializations

public class OrderBuilder {


public OrderBuilder(CustomerId customerId) { ... }

public OrderBuilder with(ProductId productId, int quantity) { ... }

public Order build() { ... }
}

order = new OrderBuilder(CustomerId.FANBOY_CUSTOMER)


.with(ProductId.JPHONE_6, 4)
.with(ProductId.CONSTELLATION_TABLET, 2)
.build();







Copyright Reaktor 2011
Monday, September 12, 2011

// default visibility
// we can only create objects with valid state
Order(CustomerId customerId, List<OrderRow> orderRows) {
this.customerId = customerId;
this.orderRows = Collections.unmodifiableList(orderRows);
}

Object state
It should be impossible to get an object in an
illegal state.
Object should be in a valid state between
any method calls.
Fail fast if the method call would leave the
object in an invalid state.

Copyright Reaktor 2011


Monday, September 12, 2011

Object state
Prefer immutability
public class Order {

private final CustomerId customerId;


private final List<OrderRow> orderRows;

Order(CustomerId customerId, List<OrderRow> orderRows) {


this.customerId = customerId;
this.orderRows = Collections.unmodifiableList(orderRows);
}

Copyright Reaktor 2011


Monday, September 12, 2011

Atomic operations
// User must explicitly begin transaction...
c.beginTransaction();
// ...do their stuff...
c.doSomething();
// ...and remember to commit...
c.commit();
// ...but what if something fails in between?

c.doInTransaction(new Callback() {
@Override
public void execute(Component c) {
// Method doInTransaction() takes
// care of transaction management
// and error handling
c.doSomething();
}
});

Copyright Reaktor 2011


Monday, September 12, 2011

BAD!!!

BETTER

Visibility and packages in Java


Domain class name is the package name:
package myapp.domain.order;

All services, interfaces and implementation


related to this domain class in the same
package.

Copyright Reaktor 2011


Monday, September 12, 2011

Visibility and packages in Java


Public visibility:
domain class,
interfaces for services,
builders, ...
public class Order { ...
public interface OrderRepository { ...
public class OrderBuilder { ...

Copyright Reaktor 2011


Monday, September 12, 2011

Visibility and packages in Java


Default visibility:
domain object constructors,
implementation classes
// domain object constructor
Order(CustomerId customerId, List<OrderRow> orderRows) { ...
// repository implementation
class JdbcOrderRepository implements OrderRepository { ...

Copyright Reaktor 2011


Monday, September 12, 2011

Domain Specific Languages


Counting two weeks from a given date:
Standard Java Calendar API
public Date twoWeeksFrom(Date beginning) {
// You have to create a
// separate Calendar object...
Calendar calendar = Calendar.getInstance();
// And modify its state...
calendar.setTime(beginning);
// And modify its state a little more...
calendar.add(Calendar.DAY_OF_MONTH, 14);
// ...before you get the answer.
return calendar.getTime();
}

Joda-Time
public DateTime twoWeeksFrom(DateTime beginning) {
return beginning.plusWeeks(2);
}

Copyright Reaktor 2011


Monday, September 12, 2011

Thank you!
Jari Mkel <jari.makela@reaktor.fi>

Ville Peurala <ville.peurala@reaktor.fi>

Copyright Reaktor 2011


Monday, September 12, 2011

Potrebbero piacerti anche