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.
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 |
| 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 |
The interface defines the contract that all implementations must follow. It specifies method signatures, return types, and whether methods are asynchronous.
Source: PlaceService.js
| 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 |
| 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 |
The
Source: services.jrl
| 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 |
The client configuration uses the SessionClientBox attaches the session id, which authenticate: true services require, and an
| 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 |
The client creates a proxy that automatically forwards method calls to the server. The
Source: ClientPlaceService.js
implements ensures this class has all interface methods (placeAutocomplete, placeDetail)Stub property automatically generates proxy methods that forward calls to delegateclientService.placeAutocomplete(...), the Stub:
delegate (SessionClientBox, then HTTPBox)delegate is set from the registration config: a SessionClientBox wrapping an HTTPBox with the service URLServices are consumed in application code via ? suffix makes the import optional, preventing failures if the service isn’t available.
| 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 |
Services should handle errors gracefully. The client receives exceptions thrown by the server implementation.
| 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
}
}
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()
});
FOAM services provide:
| Feature | Benefit |
|---|---|
| Type Safety | Interface contracts ensure consistency |
| Network Transparency | Local/remote calls work identically |
| Auto-generation | |
| Dependency Injection | Services injected via imports |
| Authentication | Built-in security integration |
| Transport Abstraction | HTTP, WebSocket, etc. supported |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Application │ │ ClientService │ │ SessionClientBox│
│ │────▶│ (Stub) │────▶│ → HTTPBox │
│ imports: │ │ │ │ (Transport) │
│ ['placeService'] │ delegate ──────┘ │ url: service/ │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│
HTTP Request
│
▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Implementation │◀────│ Skeleton │◀────│ Server │
│ │ │ (Auto-generated)│ │ │
│ GooglePlace │ │ │ │ boxClass: │
│ Service.java │ │ Deserializes │ │ ...Skeleton │
└─────────────────┘ └─────────────────┘ └─────────────────┘