API design — in search of excellence

Common design principles

Modularisation: divide and conquer.

* My Scala library does: JSON coding.* The library has a package org.my.library.coder.json that contains all related to JSON encoding.* The package has a trait Encoder that describes methods to encode JSON.* The trait has a method: Encoder.encode[T] that provides encoding objects to JSON.

Naming: a classic nightmare.

  • Concise
  • Short
  • Self-explanatory in a context — so, if you have, for example, an interface org.example.codec.json.Encoder, you don’t have to repeat some part of its name creating a method calling it encodeJson(). In this case, just encode()is good enough
  • Locally unique, to avoid using well-known common names from standard libraries, frameworks to prevent confusion
  • Follows mainstream/defacto/standard development coding styles for your computer language and environment (frameworks, company standards). For example, I’d rather not use “camelCase” for Scala/Java class names, because it is “PascalCase” is a much more common in those languages.
  • Minimising usage of obscure and unknown abbreviations — so, JSON is ok, when HLLL isn’t (of course you might have some corporate abbreviations and they mostly fine if you implement some internal software).

Minimise “conceptual weight” of your API

Encapsulate implementation details

  • interfaces/traits/type classes and instances;
  • access directives from your computer languages (like private/public/protected, import/export, etc).

Be the first user of your own API

import com.example.weather.ForecastServiceval service = new ForecastService()// I want a default implementation of prediction for current date time and some default period
service.predict()
// I want to specify some period of time for prediction
val now = LocalDateTime.now()
service.predict( untilDate = now.plusDays(1) )
// I want to specify temperature units
service.predict(
untilDate = now.plusDays(1),
temperatureUnits = TemperatureUnits.Celcius
)
// Now I want to work with the results and error handling
// Here we deciding to go with standard Either(Right,Left) from
// Scala as a monad for prediction results, so this is also is an
// API decision
service.predict() match {
case Right(prediction) => {
// get an average value of whole period of time and units
assert ( prediction.avg > .0 )
assert (
prediction.temperatureUnits == TemperatureUnits.Celcius
)
assert (
prediction.untilDate != null
)
// get avg by some period of time (for simplicity sake
// we don't specify period units like avg per hour or others)
prediction.values.foreach { pv =>
assert ( pv.avg > .0 )
}
}
case Left(err) => { // error handling
fail(err)
}
}
class A {}A a1 = new A();
A a2 = new A();
a1.equals(a2) // false
class A {  @Override
public boolean equals(Object obj) {
// ???
}
}
  • equals accepts Object, and now you have to implement equals to any possible classes
  • there is a hidden (yet well-known and documented) contract, that you also have to implement an additional method called hashCode as well
interface Eq<T> {
boolean equalsTo(T other);
}
interface Hashable {
int toHashCode();
}
class HashMap<K,V> 
// where K — is a type of key, and V is a type of value
class HashMap <K extends Hashable & Eq<K>, V>

Be consistent: don’t break your own rules

  • Follow the previously adopted rule
  • Make the previous rule obsolete, declare old definitions deprecated and provide some way to migrate for legacy code
Circle.draw(canvas, x, y, r)// use the same ordering as before
Rect.draw(canvas, x, y, w, h)
// don't do this, for instance
Rect.draw(x, y, w, h, canvas)
UserProfile (
id : String,
locale : String,
address : String
)
UserProfile(
id : UserId,
locale : Locale,
address : Address
)
case class UserId ( value : String ) extends AnyVal
newtype UserId = UserId String
Locale(
language : Language,
country : Country
)
UserProfile(
id : String,
locale : Locale,
address : Address
)

--

--

--

Software Developer & Architect

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Progressive Web Apps (PWA)

How to install Wakatime in Doom Emacs

DevOps: Operational Blindness

Magic Video Upload URLs

AWS Glue + Hudi 0.9: Bugs I faced so far

AUR packages yay in Manjaro KDE plasma — 2020

Growth of Hybrid Cloud Solutions in 2022 and Beyond

Cloud in 2017 — Opportunities and Challenges for Your Data and Applications

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abdulla Abdurakhmanov

Abdulla Abdurakhmanov

Software Developer & Architect

More from Medium

Experience During Application Framework Group Project

VIRTUAL REALITY

Scaling Systems: Horizontal v/s Vertical

Discussion Guide and Interview Insights