Skip to content

Core Abstractions

Luca_Previ0o edited this page Dec 10, 2025 · 2 revisions

Core Abstractions

The foundation of JSI rests on four fundamental abstractions: Server, Client, Request, and Response. These interfaces define the contract for all server-client communication, regardless of protocol or transport mechanism.

The Foundation Quartet

        ┌──────────┐                    ┌──────────┐
        │  Client  │────── Request ────►│  Server  │
        │          │◄───── Response ────│          │
        └──────────┘                    └──────────┘
             ▲                                ▲
             │                                │
      ConnectionClient                  ConnectionServer
      (TCP transport)                   (TCP transport)
             │                                │
        DatabaseClient                   HttpServer
      (database queries)               (HTTP protocol)

These abstractions create a protocol-agnostic framework where the same patterns work for HTTP, databases, game servers, or any custom protocol.


Server Abstraction

File: Server.java

The Server class is the purest abstraction in JSI. It defines what any server must do, with zero assumptions about transport or protocol.

Class Definition

package jsi;

public abstract class Server {
    
    /**
     * Start the server.
     */
    public abstract void start();

    /**
     * Hook method called before the server starts.
     */
    protected void onBeforeStart() {}

    /**
     * Hook method called after the server has started.
     */
    protected void onServerStarted() {}

    /**
     * Handle a request and generate a response.
     */
    public abstract Response handleRequest(Request request);
}

Key Responsibilities

  1. Lifecycle Management: start() method defines when/how server begins operation
  2. Hook Points: onBeforeStart() and onServerStarted() allow subclasses to inject custom behavior
  3. Request Handling: handleRequest() is the core business logic entry point

Design Rationale

The Server class deliberately avoids:

  • Port specification (not all servers use ports - e.g., message queue consumers)
  • Socket management (not all servers use sockets - e.g., in-process servers)
  • Threading model (could be single-threaded, thread-per-request, async)
  • Protocol details (HTTP, TCP, UDP, custom binary protocols)

This minimalism enables maximum flexibility in how servers are implemented.

Subclass Examples

// TCP-based server
public class ConnectionServer extends Server {
    // Adds: port, ServerSocket, thread-per-client
}

// HTTP-specific server
public class HttpServer extends ConnectionServer {
    // Adds: HTTP parsing, routing, response formatting
}

// Custom UDP server
public class UdpChatServer extends Server {
    // Completely custom implementation
}

Hook Method Pattern

The hook methods demonstrate the Template Method Pattern:

public void start() {
    onBeforeStart();        // Hook: Initialize resources
    // ... start server logic ...
    onServerStarted();      // Hook: Log, register with discovery service, etc.
}

Example usage:

public class LoggingHttpServer extends HttpServer {
    
    @Override
    protected void onBeforeStart() {
        System.out.println("Initializing routes...");
        // Load configuration, connect to databases, etc.
    }
    
    @Override
    protected void onServerStarted() {
        System.out.println("Server ready on port " + getPort());
        // Send notification, update service registry, etc.
    }
}

Client Abstraction

File: Client.java

The Client class is the minimal abstraction for entities that communicate with servers.

Class Definition

package jsi;

public abstract class Client {
    // Currently minimal - serves as marker for client implementations
}

Design Note

The current Client class is intentionally minimal because client behavior varies dramatically:

  • Blocking vs. non-blocking communication
  • Connection pooling strategies
  • Request multiplexing (HTTP/2, database connection reuse)

Rather than forcing a specific pattern, JSI provides concrete implementations:

ConnectionClient: TCP Client Implementation

File: ConnectionClient.java

public abstract class ConnectionClient extends Client {
    
    private String host;
    private int port;

    public ConnectionClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Response getResponse(Request request) {
        // 1. Open TCP socket to host:port
        // 2. Serialize and send request
        // 3. Read and parse response
        // 4. Close socket
        return parseResponse(responseString);
    }

    protected String serializeRequest(Request request) {
        return request.toString();
    }

    public abstract Response parseResponse(String input);
}

Key characteristics:

  • One request per connection: Simple but inefficient for high-volume scenarios
  • Synchronous: Blocks until response received
  • Protocol-agnostic: Works with any TCP-based protocol

DatabaseClient: Specialized Client

File: DatabaseClient.java

public abstract class DatabaseClient extends ConnectionClient {
    
    public DatabaseClient(String host, int port) {
        super(host, port);
    }

    @Override
    public abstract QueryResult getResponse(Request query);

    public QueryResult executeQuery(Query query) {
        return getResponse(query);
    }
}

Adds database-specific semantics (query execution) on top of TCP transport.


Request Abstraction

File: Request.java

Represents any message sent from client to server.

Interface Definition

package jsi;

/**
 * Abstract class representing a generic request.
 */
public interface Request {

    /**
     * Serialize the request into a string format.
     */
    public abstract String serialize();
}

Design Philosophy

The Request interface defines only what's universal: the ability to serialize into a transmittable format. Everything else (headers, parameters, body) is protocol-specific.

Concrete Implementations

HttpRequest

File: connection/http/HttpRequest.java

public class HttpRequest implements Request {
    
    private String path;
    private HttpRequestType requestType;  // GET, POST, etc.
    private HttpRequestParameter[] parameters;
    private HttpRequestHeader[] headers;

    public String getPath() { return path; }
    
    public Object getParameter(String name) {
        // Extract query parameter by name
    }
    
    @Override
    public String serialize() {
        // Convert to HTTP format:
        // GET /path?key=value HTTP/1.1
        // Header: value
        // ...
    }
}

Parsing example:

String rawRequest = "GET /users?id=123 HTTP/1.1\r\nHost: localhost\r\n\r\n";
HttpRequest request = new HttpRequest(rawRequest);

String path = request.getPath();           // "/users"
String id = request.getParameter("id");    // "123"

Query (Database Request)

File: connection/database/query/Query.java

public abstract class Query implements Request {

    private String rawQuery;

    public abstract QueryType getQueryType();           // SELECT, INSERT, etc.
    public abstract String getTargetCollection();       // Table name
    public abstract List<Field> getAffectedFields();    // Columns
    public abstract QueryCondition getCondition();      // WHERE clause
    
    @Override
    public String serialize() {
        return rawQuery;  // Original SQL string
    }
}

Usage example:

Query query = new MySqlQuery("SELECT * FROM users WHERE id = 123");

QueryType type = query.getQueryType();              // SELECT
String table = query.getTargetCollection();         // "users"
QueryCondition condition = query.getCondition();    // id = 123

Response Abstraction

File: Response.java

Represents any message sent from server back to client.

Interface Definition

package jsi;

/**
 * Abstract class representing a generic response.
 */
public interface Response {

    /**
     * Serialize the response to a string format suitable for transmission.
     */
    public String serialize();
}

Concrete Implementations

HttpResponse

File: connection/http/HttpResponse.java

public class HttpResponse implements Response {
    
    private ResponseType responseType;     // 200 OK, 404 NOT FOUND, etc.
    private ResponseHeader[] headers;
    private ResponseBody body;

    @Override
    public String serialize() {
        // Converts to HTTP format:
        // HTTP/1.1 200 OK
        // Content-Type: text/html
        // Content-Length: 123
        //
        // <html>...</html>
    }
}

Response components:

// Status line
ResponseType responseType = HttpResponseType.OK;  // "200 OK"

// Headers
ResponseHeader[] headers = {
    new HttpResponseHeader("Content-Type", "application/json"),
    new HttpResponseHeader("Cache-Control", "no-cache")
};

// Body
ResponseBody body = new HttpResponseBody("{\"status\": \"success\"}");

HttpResponse response = new HttpResponse(responseType, headers, body);

QueryResult (Database Response)

File: connection/database/query/QueryResult.java

public class QueryResult implements Response {
    
    private boolean success;
    private String message;
    private List<List<Field>> data;  // Rows of data

    @Override
    public String serialize() {
        // Serialize result set to string format
    }
}

Example usage:

QueryResult result = databaseEngine.execute(query);

if (result.isSuccess()) {
    List<List<Field>> rows = result.getData();
    for (List<Field> row : rows) {
        String name = row.get(1).getValue();  // Get name column
    }
}

The Request-Response Cycle

Complete Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ CLIENT SIDE                                                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Create Request                                              │
│     HttpRequest req = new HttpRequest(GET, "/users", params)    │
│                                                                 │
│  2. Serialize Request                                           │
│     String serialized = req.serialize()                         │
│     → "GET /users HTTP/1.1..."                                  │
│                                                                 │
│  3. Send over Network                                           │
│     socket.send(serialized)                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ SERVER SIDE                                                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  4. Receive Raw Data                                            │
│     String rawRequest = socket.receive()                        │
│                                                                 │
│  5. Parse Request                                               │
│     Request req = parseRequest(rawRequest)                      │
│                                                                 │
│  6. Handle Request (Business Logic)                             │
│     Response resp = handleRequest(req)                          │
│                                                                 │
│  7. Serialize Response                                          │
│     String serialized = resp.serialize()                        │
│     → "HTTP/1.1 200 OK..."                                      │
│                                                                 │
│  8. Send Response                                               │
│     socket.send(serialized)                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT SIDE                                                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  9. Receive Response                                            │
│     String rawResponse = socket.receive()                       │
│                                                                 │
│  10. Parse Response                                             │
│     Response resp = parseResponse(rawResponse)                  │
│                                                                 │
│  11. Use Response Data                                          │
│     String body = resp.getBody().getContent()                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Code Example: End-to-End

Client code:

// Create client
ConnectionClient client = new HttpClient("localhost", 8080);

// Create request
HttpRequest request = new HttpRequest(
    HttpRequestType.GET,
    new HttpRequestParameter[] {
        new HttpRequestParameter("id", "123")
    },
    null  // No custom headers
);

// Send and receive
HttpResponse response = client.getResponse(request);

// Use response
if (response.getResponseType() == HttpResponseType.OK) {
    String content = response.getBody().getContent();
    System.out.println("Response: " + content);
}

Server code:

public class MyServer extends HttpServer {
    
    @Override
    public HttpResponse handleRequest(Request request) {
        // Parse request (already done by HttpServer)
        HttpRequest httpReq = (HttpRequest) request;
        String id = httpReq.getParameter("id");
        
        // Business logic
        String userData = fetchUserFromDatabase(id);
        
        // Create response
        return new HttpResponse(
            HttpResponseType.OK,
            new ResponseHeader[] {
                new HttpResponseHeader("Content-Type", "application/json")
            },
            new HttpResponseBody(userData)
        );
    }
}

Key Design Principles

1. Protocol Independence

The same abstractions work for any protocol:

// HTTP
Request httpReq = new HttpRequest(GET, "/path", params);
Response httpResp = server.handleRequest(httpReq);

// Database
Request dbReq = new MySqlQuery("SELECT * FROM users");
Response dbResp = server.handleRequest(dbReq);

// Custom protocol
Request customReq = new GameCommandRequest("MOVE", x, y);
Response customResp = server.handleRequest(customReq);

2. Serialization as Contract

The serialize() method defines the wire format - how data travels over the network:

// HTTP serialization
GET /users?id=123 HTTP/1.1
Host: localhost

// Database query serialization
SELECT * FROM users WHERE id = 123

// Custom binary serialization
[0x01][0x00][0x7B][...]  // Your custom format

3. Extensibility Through Composition

Add functionality by composing request/response objects:

// Request with authentication
public class AuthenticatedHttpRequest extends HttpRequest {
    private String authToken;
    
    @Override
    public String serialize() {
        return super.serialize() + "\nAuthorization: Bearer " + authToken;
    }
}

// Response with caching
public class CachedHttpResponse extends HttpResponse {
    @Override
    public String serialize() {
        String base = super.serialize();
        return base.replace("\r\n\r\n", 
            "\r\nCache-Control: max-age=3600\r\n\r\n");
    }
}

Related Documentation


Next: Dive into ConnectionServer to see how TCP transport is built on these abstractions, or explore HTTP Server for a complete protocol implementation.

Clone this wiki locally