interfaces

why they are important and why you should pay close attention to interface definition

Gergő Pintér, PhD

gergo.pinter@uni-corvinus.hu

an interface is a shared boundary across which two or more separate components of a computer system [1]

hardware interfaces by Pittigrilli | CC BY-SA

Ethernet 1000BASE-T | DisplayPort 1.2 | USB 2.0

user interface KDE Plasma 5.22 from Wikimedia CC BY-SA

interface is an agreement

  • how a module / component will work
  • so as long as the agreement is complied the components do not need to know about the internal structure/work of the other components
    • separation of concerns
    • single responsibility principle

a 327 Million Dollar interface miscommunication

NASA and Lockheed Martin mixed up units for the Mars Climate Orbiter (1999)

  • spacecraft sent values back to Earth in Newton seconds (kg⋅m/s)
  • the software in the ground station read those results as pound seconds
    • the guidance and navigation teams was off by a factor of 4.45 times
  • this led to a miscalculated trajectory, which doomed the probe and the mission
rendering of the Mars Climate Orbiter by NASA/JPL/Corby Waste via Wikipedia [2]

based on [3], read the full story written by Tim Dodd

hexagonal arcitectural pattern (a.k.a. ports & adapters)

the most important elements of the design are the interfaces, hexagonal arcitecture focuses on them

user statistics example - interfaces

data sent to the UI

specify
  • the JSON schema
  • endpoint
  • etc.

user statistics - C4 component

interface mocking

  • an interface is a boundary where a module can be separated
  • in the user statistics example there are two parts: a backend and a frontend
  • with a well defined interface, the frontend can work regardless of the backends’s state
    • e.g., using a mock backend

A mock, in software engineering, is a simulated object or module that acts as a stand-in for a real object or module [4].

user statistics - mock backend

require 'sinatra'

def generate_progress
  rand.round(2)
end

def generate_activity_matrix
  result = []
  (1..4).each do |_w|
    daily = []
    (1..7).each do |_d|
      daily.push rand(10)
    end
    result.push daily
  end
  result
end

get '/user/:id/statistics' do
  data = {}
  data['name'] = 'Marvin'
  data['id'] = params['id']
  data['registration'] = '2019-10-02'
  data['progress'] = generate_progress
  data['activity'] = generate_activity_matrix
  return data.to_json
end

http://localhost:4567/user/42/statistics

{
    "name": "Marvin",
    "id": 42,
    "registration": "2019-10-02",
    "progress": 0.92,
    "activity": [
        [4,9,7,4,7,1,8],
        [9,8,1,8,4,1,7],
        [3,6,8,4,2,4,5],
        [3,5,5,3,2,9,7]
    ]
}

frontend development

a mock backend should be enough for a frontend developer to create and test the user statistics view of the user interface

{
    "name": "Marvin",
    "id": 42,
    "registration": "2019-10-02",
    "progress": 0.92,
    "activity": [
        [4,9,7,4,7,1,8],
        [9,8,1,8,4,1,7],
        [3,6,8,4,2,4,5],
        [3,5,5,3,2,9,7]
    ]
}

it may be presented to the customer

fast feedback, agile, and so on…

do not change the interface (without notice)

float: 0–1
integer: 0–100

this will break the frontend

it is not just rude, but will waste the time of the other team (with pointless debugging)

do no break the userland

the number one rule of kernel development is that “we don’t break users”

Linus Torvalds

API changes should be communicated

  • during design / development
    • change can be necessary / allowed, but communicate towards the impacted teams
    • diagrams show inner dependencies
  • services announce API deprecations
  • so as library / framework developers
    • can be a source of new issues even if downstream code is not changed

API versions

  • 2021-06-30: API v1’s end of live
    • service does not accept connections via APIv1
    • code can be removed (no need to maintain it anymore)

https://developers.facebook.com/v21.0/me?fields=id,name

language level

GeoPandas 1.0 / new deprecations: unary_union attribute is now deprecated and replaced by the union_all() method (#3007) allowing opting for a faster union algorithm for coverages (#3151)

from shapely import Polygon
import geopandas as gpd

p1 = Polygon([[1, 2], [3, 2], [3, 4], [1, 4]])
p2 = Polygon([[2, 3], [4, 3], [4, 5], [2, 5]])

gpd.GeoDataFrame(geometry=[p1, p2]).unary_union

DeprecationWarning: The ‘unary_union’ attribute is deprecated, use the ‘union_all()’ method instead.

def unary_union(self):
    warnings.warn(
        "The 'unary_union' attribute is deprecated, "
        "use the 'union_all' method instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return self.union_all()

java

public class Worker {
    /**
     * Calculate period between versions
     * @deprecated
     * This method is no longer acceptable to compute time between versions.
     * <p> Use {@link Utils#calculatePeriod(Machine)} instead.
     *
     * @param machine instance
     * @return computed time
     */
    @Deprecated(since = "4.5", forRemoval = true)
    public int calculate(Machine machine) {
        return machine.exportVersions().size() * 10;
    }
}

source: [5]

IDEs can parse the deprecation decorators and show to the developer during work

discussion: Hyrum’s Law

With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.

– Hyrum Wright, 2012

by Randall Munroe CC BY-NC 2.5 | source

example to Hyrum’s Law

/user/<id>/statistics

{
    "name": "Marvin",
    "id": 42,
    "registration": "2019-10-02",
    "progress": 0.92,
    "activity": [
        [4,9,7,4,7,1,8],
        [9,8,1,8,4,1,7],
        [3,6,8,4,2,4,5],
        [3,5,5,3,2,9,7]
    ]
}

no documented /users endpoint, but existing implementation

get '/users' do
  data = {"ids" => [42]}
  return data.to_json
end

sooner or later someone will discover the undocumented behavior and depend on it

think like a villian, someone who’s going to abuse the system that you’re designing and building

discussion: Postel’s law

Be conservative in what you do, be liberal in what you accept from others.

– Jon Postel, in the specification of TCP in 1980 (RFC 761) source: Postel’s Law

Postel’s law / robustness principle

When receiving data, handle variations, minor errors, or deviations where possible. Don’t crash or reject communication over minor issues.

When your system emits data or interacts with the outside world, adhere closely to protocols and standards.

In modern times, overly liberal acceptance can sometimes mask errors, so this law is occasionally tempered with security considerations.

references

[1]
Wikipedia contributors, “Interface (computing) — Wikipedia, the free encyclopedia.” https://en.wikipedia.org/w/index.php?title=Interface_(computing)&oldid=1244878409, 2024.
[2]
Wikipedia contributors, “Mars climate orbiter — Wikipedia, the free encyclopedia.” https://en.wikipedia.org/w/index.php?title=Mars_Climate_Orbiter&oldid=1248652445, 2024.
[3]
T. Dodd, “Metric vs imperial units: How NASA lost a 327 million dollar mission to mars.” https://everydayastronaut.com/mars-climate-orbiter/ , 14-Mar-2020.
[4]
GeeksforGeeks, “MOCK (introduction) – software engineering.” https://www.geeksforgeeks.org/software-engineering-mock-introduction/ , 26-Apr-2019.
[5]
H. Ozler, “Java @deprecated annotation.” https://www.baeldung.com/java-deprecated , 02-Jan-2019.