foam3

FOAM Services By Example

FOAM services implement business logic and coordinate between DAOs. Services provide a clean abstraction for remote procedure calls, allowing client code to call server methods as if they were local.

Overview

A complete FOAM service consists of four components:

Component Purpose File
Interface Defines the contract PlaceService.js
Registration Configures client-server communication services.jrl
Client Proxy that forwards calls to server ClientPlaceService.js
Implementation Server-side business logic GooglePlaceService.js

When to Use Services vs DAOs

Use Case Mechanism
CRUD operations on data DAO
Complex business logic Service
Operations spanning multiple DAOs Service
External API integration Service
Stateless request/response Service

Service Interface Definition

The interface defines the contract that all implementations must follow. It specifies method signatures, return types, and whether methods are asynchronous.

Source: PlaceService.js

foam.INTERFACE({ package: 'foam.core.place', name: 'PlaceService', skeleton: true, // Generates server-side skeleton class automatically methods: [ { name: 'placeAutocomplete', async: true, // Method returns Promise args: 'Context x, String input, String preferCountry', type: 'foam.core.place.PlaceAutocompleteResp' // Return type }, { name: 'placeDetail', async: true, args: 'Context x, String placeId', type: 'foam.core.place.PlaceDetailResp' } ] }); log('PlaceService interface defined');

Interface Properties

Property Purpose
package Namespace for the service
name Service interface name
skeleton: true Auto-generates PlaceServiceSkeleton class for server
methods Service operations with signatures and return types

Method Properties

Property Purpose
async: true Enables Promise-based calls
args Method parameters (Context x is conventionally first; the client stub sends it as null)
type Return type specification

Service Registration

The configures the service for client-server communication, specifying authentication requirements, transport mechanism, and implementation class.

Source: services.jrl

p({ "class": "foam.core.boot.CSpec", "name": "placeService", "authenticate": true, // Requires authentication "serve": true, // Expose as server endpoint "boxClass": "foam.core.place.PlaceServiceSkeleton", // Auto-generated skeleton "serviceClass": "foam.core.place.google.GooglePlaceService", // Java implementation // Client-side proxy config. "client" is a JSON string, not a nested object. "client": "{\"class\":\"foam.core.place.ClientPlaceService\",\"delegate\":{\"class\":\"foam.box.SessionClientBox\",\"delegate\":{\"class\":\"foam.box.HTTPBox\",\"url\":\"service/placeService\"}}}" }); log('PlaceService registration configured');

Registration Properties

Property Purpose
name Service identifier for dependency injection
authenticate Security requirement
serve Makes service available over network
boxClass Server-side message handler (auto-generated from skeleton: true)
serviceClass Concrete implementation class
client Client-side configuration, as a JSON string

Client Configuration

The client configuration uses the pattern. A SessionClientBox attaches the session id, which authenticate: true services require, and an carries the call:

Property Purpose
class Client proxy class
delegate.class SessionClientBox, which adds the session id
delegate.delegate.class Transport mechanism (HTTPBox for HTTP)
delegate.delegate.url Service URL endpoint

Client Implementation

The client creates a proxy that automatically forwards method calls to the server. The property class handles all serialization and network communication.

Source: ClientPlaceService.js

foam.CLASS({ package: 'foam.core.place', name: 'ClientPlaceService', implements: ['foam.core.place.PlaceService'], // Must implement the service interface properties: [ { class: 'Stub', // Special property class that creates method proxies of: 'foam.core.place.PlaceService', // Interface to proxy name: 'delegate' // Will be set to HTTPBox from registration } ] }); log('ClientPlaceService proxy defined');

How the Client Works

  1. implements ensures this class has all interface methods (placeAutocomplete, placeDetail)
  2. The Stub property automatically generates proxy methods that forward calls to delegate
  3. When you call clientService.placeAutocomplete(...), the Stub:
    • Serializes the method call and arguments
    • Sends the request via the delegate (SessionClientBox, then HTTPBox)
    • Deserializes the response and returns it
  4. delegate is set from the registration config: a SessionClientBox wrapping an HTTPBox with the service URL

Service Usage

Services are consumed in application code via . The ? suffix makes the import optional, preventing failures if the service isn’t available.

foam.CLASS({ package: 'foam.demo', name: 'PlaceDemo', imports: [ 'placeService?' // Import service (? makes it optional) ], methods: [ { name: 'searchPlaces', code: async function(query) { if ( ! this.placeService ) { console.log('Place service not available'); return; } try { // Call service method var result = await this.placeService.placeAutocomplete( this.__subContext__, query, 'US' ); console.log('Found places:', result.predictions); return result; } catch (error) { console.error('Place search failed:', error); } } } ] }); log('Service usage example defined');

Usage Patterns

Pattern Purpose
imports: ['placeService?'] Dependency injection with optional flag
this.__subContext__ Placeholder for the Context x argument; the client stub sends null and the server uses its own context
async/await Promise-based method calls
try/catch Error handling for network/service failures

Error Handling

Services should handle errors gracefully. The client receives exceptions thrown by the server implementation.

Common Error Patterns

Pattern Use Case
Return null/empty No results found
Throw RuntimeException Unexpected errors
Return error object Expected failures with details
// Client-side error handling
try {
  var result = await this.myService.doSomething(this.__subContext__, args);
  if ( ! result ) {
    // Handle no results
  }
} catch (error) {
  if ( error.message.includes('PermissionDenied') ) {
    // Handle permission error
  } else {
    // Handle unexpected error
  }
}

Testing Services

Services can be tested by mocking the delegate or using a local implementation.

// Create a mock service for testing
foam.CLASS({
  name: 'MockPlaceService',
  implements: ['foam.core.place.PlaceService'],

  methods: [
    async function placeAutocomplete(x, input, preferCountry) {
      return foam.core.place.PlaceAutocompleteResp.create({
        predictions: [{ description: 'Test Place', placeId: 'test-123' }]
      });
    }
  ]
});

// Inject mock in tests
var ctx = foam.createSubContext({
  placeService: MockPlaceService.create()
});

Summary

FOAM services provide:

Feature Benefit
Type Safety Interface contracts ensure consistency
Network Transparency Local/remote calls work identically
Auto-generation classes created automatically
Dependency Injection Services injected via imports
Authentication Built-in security integration
Transport Abstraction HTTP, WebSocket, etc. supported

Complete Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Application   │     │  ClientService  │     │ SessionClientBox│
│                 │────▶│     (Stub)      │────▶│   → HTTPBox     │
│  imports:       │     │                 │     │   (Transport)   │
│  ['placeService']    │  delegate ──────┘     │  url: service/  │
└─────────────────┘     └─────────────────┘     └────────┬────────┘
                                                         │
                                                    HTTP Request
                                                         │
                                                         ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Implementation │◀────│    Skeleton     │◀────│     Server      │
│                 │     │  (Auto-generated)│     │                 │
│  GooglePlace    │     │                 │     │  boxClass:      │
│  Service.java   │     │  Deserializes   │     │  ...Skeleton    │
└─────────────────┘     └─────────────────┘     └─────────────────┘

See Also