“The ratio of time spent reading versus writing is well over 10 to 1.” – Robert C. Martin
Inspired by this fact, I present programming principles, design patterns and the benefits designing your API in favor of the caller of your module to achieve a highly maintainable code base. Through the use of numerous examples, you’ll learn how to design a module that is convenient to use, easy to maintain and hard to break.
For more information see also the related blog post: https://tech.innogames.com/boost-your-development-with-proper-api-design/
2. ABOUT ME
Senior developer @InnoGames
I develop backends for millions of players worldwide
Started programming in the age of 12
In the gaming industry since 2014
www.code-held.com
@MarHeldro
3. MOTIVATION
Faster development speed with well-designed
architecture
Isolated modules lead to isolated technical depth
Its more fun to use a well-designed module
Read further in our InnoGames Techblog
tech.innogames.com
4. TABLE OF CONTENTS
01 Domain Design
02 Value Objects And Entities
03 Model Integrity
04 API Design
10. DOMAIN DESIGN
HOW TO FIND YOUR DOMAIN
Think about data and their relations
DOMAIN DESIGN
HOW TO FIND YOUR DOMAIN
Think about data and their relations
12. VALUE OBJECTS AND ENTITIES
ENTITY
Identified by ID
Our apartment is an entity and its ID is the address
public class ResourceEntity {
private final String name;
private MoneyVO price;
[...]
}
private final ResourceIdVO id;
13. VALUE OBJECTS AND ENTITIES
VALUE OBJECT
Described by its attributes
An apple is described by its color, size and type
Should be immutable
public class MoneyVO {
private BigDecimal value;
[...]
}
16. MODEL INTEGRITY
EVERY MODEL SHOULD EXCLUSIVELY HOLD, TRANSFORM OR MODIFY INFORMATION IT HOLDS
public class UserEntity {
private MoneyVO money;
}
private boolean isBankrupt;
17. MODEL INTEGRITY
EVERY MODEL SHOULD EXCLUSIVELY HOLD, TRANSFORM OR MODIFY INFORMATION IT HOLDS
Different presentation of the same information should derive by a transformation method
public class UserEntity {
private MoneyVO money;
public boolean isBankrupt() {
return money.isNegative();
}
}
18. MODEL INTEGRITY
EVERY MODEL SHOULD EXCLUSIVELY HOLD, TRANSFORM OR MODIFY INFORMATION IT HOLDS
Class is responsible to ensure validity of its data
public class UserEntity {
private MoneyVO money;
public void subtractMoney(MoneyVO value) throws NotEnoughMoneyException {
MoneyVO subtract = money.subtract(value);
if (subtract.isNegative()) {
throw new NotEnoughMoneyException(money, value);
}
money = subtract;
}
}
→ Changes are only allowed through internal methods
19. PROVIDE FACTORIES
Enables you to restrict visibility
You can use injected dependencies
public class ResourceFactory {
private final ResourceIdGenerator resourceIdGenerator;
[...]
create(String name, String price) {
return new ResourceEntity(
resourceIdGenerator.newId(),
name,
new MoneyVO(price)
);
}
}
private final ResourceIdGenerator resourceIdGenerator;
resourceIdGenerator.newId(),
ResourceEntity
20. PACKAGE STRUCTURE
You usually search with three different intentions:
1. You know the name of a class
2. You know the interface or superclass
3. You know the feature it belongs to
→ Search function of your IDE
→ Hierarchy of your IDE
→ Package structure
23. EXPOSE A THIN BUT DESCRIPTIVE API
The API is EVERYTHING that can be seen from the outside
lowest visibility as necessary
Every constant should be public
The exposed API should be as
easy to use as possible
24. PROVIDE CLASS DOCUMENTATION
What does the class do?
Which data does the class hold or manipulate?
How should the class be used (with examples)?
Is your class thread safe?
/**
* Represents a resource. Every resource has a unique {@link ResourceIdVO}.
* <p>
* Use {@link ResourceFactory#create(String, String)} to generate new instances.
*/
public class ResourceEntity {
Every resource has a unique {@link ResourceIdVO}.
Use {@link ResourceFactory#create(String, String)} to generate new instances.
25. WRITE DESCRIPTIVE SIGNATURES
Every public method should be understandable without looking into its body
Best solution: The signature describes the functionality
public int buy(String user1, String user2, String resource, int amount)String user1, String user2 String resourceint
26. WRITE DESCRIPTIVE SIGNATURES
Every public method should be understandable without looking into its body
Best solution: The signature describes the functionality
public int buy(String user1, String user2, String resource, int amount)
public TransactionId buy(UserId buyer, UserId seller, String resourceName, int amount)
String user1, String user2 String resourceint
UserId buyer, UserId seller String resourceNameTransactionId
27. DESCRIBE THE CONTRACT OF YOUR METHODS
You can't describe everything in the signature
The contract of a method describes the boundaries and side effects
/**
* Creates a new {@link ResourceEntity} with a unique id. This method is thread-safe.
*
* @param name The name of the resource
* @param price The price of the resource. The String must be interpretable by
* {@link BigDecimal#BigDecimal(String)}. A price must be positive.
* @return A new {@link ResourceEntity}
*/
ResourceEntity create(String name, String price);
unique id This method is thread-safe.
The String must be interpretable by
{@link BigDecimal#BigDecimal(String)} A price must be positive.
28. DESCRIBE THE CONTRACT OF YOUR METHODS
You can't describe everything in the signature
The contract of a method describes the boundaries and side effects
/**
* Creates a new {@link ResourceEntity} with a unique id. This method is thread-safe.
*
* @param name The name of the resource
* @param price The price of the resource. The String must be interpretable by
* {@link BigDecimal#BigDecimal(String)}. A price must be positive.
* @return A new {@link ResourceEntity}
*/
ResourceEntity create(String name, String price);
unique id This method is thread-safe.
The String must be interpretable by
{@link BigDecimal#BigDecimal(String)} A price must be positive.
ResourceEntity create(String name, String price) {
return new ResourceEntity(
resourceIdGenerator.newId(),
name,
new MoneyVO(price)
);
} private class ResourceIdGenerator {
// Use AtomicInteger to make the generator thread safe.
private AtomicInteger counter = new AtomicInteger(0);
ResourceIdVO newId() {
return new ResourceIdVO(counter.incrementAndGet());
}
}
29. DESCRIBE THE CONTRACT OF YOUR METHODS
ResourceEntity createWithUniqueIdThreadSafe(
String name,
String positiveBigDecimalInterpretablePrice
);
NO.
31. MAKE YOUR DEEPEST LAYER THE PRETTIEST
The deeper
the more…
… effort
… expensive
… used
32. CONCLUSION
Make it as easy as possible for the caller to use
your module
The code must be easy to read, not to write
"The ratio of time spent reading versus writing is
well over 10 to 1."
- Robert C. Martin (Clean Code, 2009)
33. IT ALL COMES TO AN END…
www.code-held.com
@MarHeldro