LearningTree

Designing
Interfaces

Interface styles, call behaviors, data formats, and best practices for designing well-structured, resilient software interfaces.

01
Chapter One

Overview and Basic Properties of Designing Interfaces

One of the most important aspects of an architecture are interfaces and the relationships between components.

Interfaces — The System Backbone

Interfaces define the structure and behavior of a system. They provide the contracts between components, enable loose coupling, and coordinate complex networks of subsystems.

🏗️

Structure

  • Define the structure and behavior of the system
  • Express contracts between components
🔗

Connectivity

  • Provide a link between specialized components
  • Enable loose coupling of parts
⚙️

Orchestration

  • Coordinate and orchestrate a complex network of subsystems
  • Manage data flows between components
System Components & Interface Flow
Diagram: Interfaces link a network of components — Input enters the system, traverses interconnected components via defined interfaces, and exits as Output.
Interface Network — Input → Components → Output
SYSTEM BOUNDARY Input Output
Three Basic Properties of Interfaces

Every interface can be described along three fundamental dimensions: its style (resource-oriented vs. service-oriented), its call behavior (synchronous vs. asynchronous), and its data format (how data is structured and exchanged).

🎨

Interface Style

  • Resource-oriented — based on manipulating resources
  • Service-oriented — based on encapsulated business logic
📞

Call Behavior

  • Synchronous — caller waits for a response
  • Asynchronous — caller continues without waiting
📦

Interface Data Format

  • Exchanging / transferring data
  • Defines how data is structured and serialized
Resource-Oriented Approach (REST)

Focuses on defining and manipulating resources (images, books, users, products, etc.). The number of resources is arbitrary, but the number of operations is limited (CRUD-based HTTP verbs). The most common example is REST (REpresentational State Transfer).

🌐

Resource-Oriented — Key Points

  • Focuses on defining and manipulating resources
  • The number of resources is arbitrary
  • The number of operations is limited (CRUD-based HTTP verbs)
Example: REST — business objects referenced as “Web resources” via URI over HTTP(S).
REST — Web Client / Server / Resource
Web Client textual payload Web Server GET, POST, PUT, PATCH, and DELETE queries Web Resource identified by URI
REST API — Operations Using HTTP Methods
OperationHTTP VerbURI Example
Retrieve a specific book GET /books/{bookId}
List all books GET /books
Create a new book POST /books
Update a specific book PUT /books/{bookId}
Delete a specific book DELETE /books/{bookId}
Service-Oriented Approach (SOAP / gRPC)

Centered around defining and accessing services that encapsulate business logic. We can define a wide range of operations, allowing for more complex interactions. Method calls are encapsulated in messages.

🔧

Service-Oriented — Key Points

  • Centered around defining and accessing services (encapsulating business logic)
  • We can define a wide range of operations
  • Allows for more complex interactions
  • Method calls are encapsulated in messages

Protocols: SOAP (XML via HTTPS), gRPC (Protocol Buffers via HTTP 2.0)

Service-Oriented — Client / Server / Service
Client serialized function call serialized return value Server function invocation return value Service
Service-Oriented — gRPC Example (CRUD)

Below is a Protocol Buffers service definition for a bookstore. The four standard CRUD methods (Add, Get, Update, Delete) are complemented by specialized operations like SearchBooks and BulkUpdateInventory — illustrating the key advantage of service-oriented interfaces: an unlimited number of custom operations.

syntax = "proto3";
package bookstore;

service BookManagement {

    // โ”€โ”€ Standard CRUD operations โ”€โ”€
    rpc AddBook     (AddBookRequest)     returns (AddBookResponse);
    rpc GetBook     (GetBookRequest)     returns (GetBookResponse);
    rpc UpdateBook  (UpdateBookRequest)  returns (UpdateBookResponse);
    rpc DeleteBook  (DeleteBookRequest)  returns (DeleteBookResponse);

    // โ”€โ”€ Specialized operations beyond CRUD โ”€โ”€
    rpc SearchBooks          (SearchBooksRequest)          returns (SearchBooksResponse);
    rpc BulkUpdateInventory  (BulkUpdateInventoryRequest)  returns (BulkUpdateInventoryResponse);
}
Extensibility: Service-oriented interfaces can extend operations like Recommendations, adding domain-specific logic beyond standard CRUD.
gRPC Example — Extended: Recommendation Service

Service-oriented interfaces are not limited to basic CRUD. Here we extend the bookstore with a dedicated RecommendationService that exposes domain-specific operations — suggesting books by reading history, genre, trending popularity, or author. Each RPC encapsulates complex business logic behind a single, clearly named call.

syntax = "proto3";
package bookstore;

// โ”€โ”€ The recommendation service definition โ”€โ”€
service RecommendationService {

    rpc RecommendBooksByHistory           (RecommendationRequest)    returns (RecommendationResponse);
    rpc RecommendBooksByGenre             (RecommendByGenreRequest)  returns (RecommendationResponse);
    rpc GetTrendingBooks                  (TrendingRequest)          returns (RecommendationResponse);
    rpc RecommendBooksByAuthorPopularity  (RecommendationRequest)    returns (RecommendationResponse);
}
Synchronous vs. Asynchronous — Call Behavior
⏱️

Synchronous

  • Straightforward and easy to understand
  • Linear flow, well-defined order
  • Ideal when the result is immediately required
  • Simple CRUD operations
Caller
Request
Process
Response
Continue
🔄

Asynchronous

  • Ideal when the caller can continue executing without waiting
  • The result cannot be available immediately
  • Useful for long-running operations (e.g., report generation)
Caller
Send & Go
Process
Callback
Example: Triggering a Report generation — caller continues while the system processes asynchronously.
Interface Data Format — REST over HTTP Example

The data format defines how data is structured and exchanged between components. In REST over HTTP, the request and response follow standardized formats including headers and content negotiation.

Request
GET /index.html HTTP/1.1 Host: www.mycompany.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Successful Response
HTTP/1.1 200 OK
Content-Length: 438
Content-Type: text/html; charset=UTF-8
Failed Response
HTTP/1.1 406 Not Acceptable
Content-Type: text/plain; charset=UTF-8
Content-Length: 138
Connection: close
Designing Interfaces — Basic Properties
1
Interface Style
Resource-oriented or service-oriented
2
Call Behavior
Synchronous or asynchronous
3
Interface Data Format
Exchanging / transferring data
Internal vs. External Interfaces
Internal Interfaces
External Interfaces
  • Consumers are known — same team or organization
  • Lower risk of misuse
  • Easier to change and evolve
  • Less formal documentation required
  • Unknown consumers — public or third-party
  • Higher opportunities for misuse
  • Strict security requirements
  • Strong stability requirements — breaking changes are costly
📋 Chapter 1 — Summary
  • Interfaces define the structure, connectivity, and orchestration of a system
  • Interface Style: Resource-oriented (REST) vs. Service-oriented (gRPC/SOAP)
  • Call Behavior: Synchronous (caller waits) vs. Asynchronous (caller continues)
  • Data Format: Defines how data is structured and exchanged (e.g., HTTP request/response)
  • Internal interfaces: known consumers, easier to evolve
  • External interfaces: unknown consumers, strict security and stability requirements
02
Chapter Two

Requirements and Best Practices for Designing Good Interfaces

Good Interface Requirements

A well-designed interface must satisfy several key requirements: it should fulfill user needs, be easy to learn and use, easy to extend, hard to misuse, consistent across the system, and meet quality requirements.

🎯

Fulfill User Requirements

  • Suitable functionality — fit clients' needs
  • Difficult for public APIs (unknown consumers)
  • Provide what the consumer actually needs
📖

Easy to Learn & Use

  • Provide IDL (e.g., by using Swagger)
  • Use unit tests as documentation
  • Self-descriptive naming conventions
🔌

Easy to Extend

  • Consider API versioning
  • Design for backward compatibility
  • Allow adding operations without breaking consumers
🛡️

Hard to Misuse

  • Both intentional and unintentional misuse
  • Validate inputs at the boundary
  • Return meaningful error messages
🔗

Consistent

  • Consistent to other interfaces in the system
  • Same naming conventions throughout
  • Uniform error response structures

Quality Requirements

  • Performance — latency, throughput
  • Security — auth, encryption
  • Robustness — fault tolerance
Best Practices for Designing Interfaces
🐕

Use It Yourself / Dog-Fooding

  • Implement tests and use tests as a reference example
  • Consider perspective of consumers and extended service providers
  • Test for correctness at boundary conditions
🤝

Clarify Requirements

  • Clarify with the consumer and provider
  • Clarify different views or requirements
  • Align on contracts before implementation

Hide Implementation Details — Information Hiding Principle: Consumers should interact with an interface without knowing internal implementation details. This allows the implementation to change freely as long as the interface contract remains stable.

Use Meaningful, Self-Descriptive Names

Use terms from the domain model. Resources should be named after business concepts. Avoid generic names like elements, object, or resource.

✓ Good Names
http://your-domain.com/books/01
http://your-domain.com/authors
http://your-domain.com/photos/photo-0123.jpg
✗ Names to Avoid
http://your-domain.com/elements
http://your-domain.com/object
http://your-domain.com/resource
http://your-domain.com/context
http://your-domain.com/items
http://your-domain.com/instances
http://your-domain.com/entries
http://your-domain.com/value
Naming Message / Data Fields
Rule: Use full, self-descriptive field names in API contracts. Abbreviations like id and ctr are acceptable as they are general or domain-specific, but opaque abbreviations like sd, ed should be expanded to startDate, endDate.
✗ Before — Ambiguous Fields
{
  "title": "Summer Sale - Up to 50% Off!",
  "id": "123",
  "sd": "2024-06-01T00:00:00Z",
  "ed": "2024-06-30T23:59:59Z",
  "ctr": 0.79,
  "budget": 250,
  "duration": 24
}
✓ After — Self-Descriptive Fields
{
  "title": "Summer Sale - Up to 50% Off!",
  "id": "123",              // "identifier" โ€” OK
  "startDate": "2024-06-01T00:00:00Z",
  "endDate": "2024-06-30T23:59:59Z",
  "ctr": 0.79,              // "click-through rate" โ€” OK
  "budget": {"amount": 250, "currency": "usd"},
  "duration_hours": 24
}
Service-Oriented Interface Operation — Names

Use clear naming conventions: Verbs for actions on the service itself (e.g., Archive, Create) and VerbNouns for actions on specific resources (e.g., AddBook, GetBook).

syntax = "proto3";
package bookstore;

service BookLibrary {

    // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    //  Pattern 1 :  Verbs
    //  โ†’ operate on the service itself (no resource noun needed)
    // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    rpc Archive   (ArchiveLibrary)   returns (Status);
    rpc Create    (CreateRequest)    returns (Status);


    // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    //  Pattern 2 :  VerbNouns
    //  โ†’ operate on a specific resource (verb + resource name)
    // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    rpc AddBook      (AddBookRequest)      returns (AddBookResponse);
    rpc GetBook      (GetBookRequest)      returns (GetBookResponse);
    rpc UpdateBook   (UpdateBookRequest)   returns (UpdateBookResponse);
    rpc DeleteBook   (DeleteBookRequest)   returns (DeleteBookResponse);
    rpc SearchBooks  (SearchBooksRequest)  returns (SearchBooksResponse);

}
Minimize Surprises and Side Effects

The Principle of Least Surprise: an interface should behave exactly as a consumer would expect. No hidden mutations, no unexpected state changes.

Example 1: Update User's First Name

Operation: Update first name only
Expectation: Only the first name changes

✗ Unexpected Side-Effects:
— Password is reset
— Preferences are reset

Example 2: getUserById(UserId)

Operation: Retrieve user by ID
Expectation: Returns User or fails gracefully

✗ Unexpected Side-Effect:
— Creates a new User instead of failing
Providing “Atomic” Information Units

Return data as separate, type-safe fields — not bundled strings that require parsing. Each piece of data should be independently addressable and evaluable.

✓ Good Example — Atomic Endpoints
GET /api/users/{userId}/name
→ Response: "James Smith"

GET /api/users/{userId}/email
→ Response: "james.smith@gmail.com"

GET /api/users/{userId}/birthdate
→ Response: "08-15-2001"
✗ Bad Example — Non-Atomic (Bundled)
GET /api/users/{userId}/profile

→ Response:
"James Smith; james.smith@gmail.com ; 08-15-2001"
⚠ Bundled string requires parsing — not atomic, not type-safe
Best Practices — Overview
📐

Design Principles

  • Use meaningful, self-descriptive names — use terms from the domain model
  • Minimize surprises and side effects (principle of least surprise)
  • Provide “atomic” information units — easy to evaluate, no parsing of strings
  • Documentation (especially for boundary / threshold examples and errors)
⚙️

Process Practices

  • Use it yourself — implement tests as reference examples
  • Clarify requirements with the consumer and provider
  • Hide implementation details (information hiding principle)
  • Provide IDL (Swagger, OpenAPI, proto) for discoverability
  • Consider API versioning for extensibility
📋 Chapter 2 — Summary
  • 6 Requirements for Good Interfaces: Fulfill user needs, easy to learn & use, easy to extend, hard to misuse, consistent, meet quality requirements
  • Dog-fooding: Use your own API — write tests as reference examples
  • Clarify requirements with both consumer and provider before implementation
  • Information Hiding: Consumers should not know internal implementation details
  • Meaningful names: Use domain model terms; avoid generic names; use VerbNoun for services
  • Principle of Least Surprise: No hidden side effects or unexpected state changes
  • Atomic information units: Return type-safe, individually addressable fields
  • Documentation: Provide IDL (Swagger/OpenAPI), document boundaries and error responses
Summary โ€” Interface Design at a Glance
01 ยท Overview & Basic Properties

What Makes an Interface

  • Interfaces connect components โ€” every system boundary is an interface
  • Internal (module-to-module) vs. external (system-to-system)
  • Resources, operations, data types, error signals & quality properties
  • Interfaces are the hardest thing to change โ€” design them carefully
02 ยท Requirements & Best Practices

Designing Good Interfaces

  • 6 requirements: useful, learnable, extensible, hard to misuse, consistent, quality-aware
  • Dog-food your own API โ€” write tests as reference examples
  • Information Hiding โ€” never expose internals to consumers
  • Meaningful names, no side effects, atomic return types
  • Document with IDL (Swagger/OpenAPI); version for extensibility