Skip to content

Services

import 'package:gisila/gisila.dart';

Base class for per-request business logic. The framework creates a fresh instance per request (on first use), attaches the request’s RequestContext and caches it for the rest of that request.

abstract class Service {
Service();
RequestContext get ctx;
T db<T>();
T singleton<T extends Object>();
void attach(RequestContext context);
}

When to use: put business rules and data access in services and keep controllers thin. A service can read the principal, the database session and app-wide singletons without them being passed through every call. Use a singleton instead for stateless, shareable clients (HTTP clients, mailers), and a plain function for pure logic that needs no request state.

Member Description
ctx The request context. Throws StateError if read before attach (only happens when you construct a service yourself and forget to attach it).
db<T>() Same as ctx.db<T>().
singleton<T>() Same as ctx.singleton<T>().
attach(context) Wires the service to a context. The framework calls it; call it yourself in unit tests.

To use a service, register a factory and declare a parameter of the service type on a controller method (or call ctx.service<T>()). Services can depend on other services through ctx.service<Other>(); each type is created once per request.

Example

class OrderService extends Service {
Future<Order> place(OrderForm form) async {
final userId = ctx.principal!.id;
final payments = ctx.service<PaymentService>();
await payments.charge(userId, form.total.value!);
return orders.insert(userId: userId, total: form.total.value!);
}
}
// main():
app
..registerService<OrderService>(OrderService.new)
..registerService<PaymentService>(() => PaymentService(app.singleton<StripeClient>()));
// controller:
@Post('/orders')
@RequireAuth()
Future<Order> place(OrderForm form, OrderService orders) => orders.place(form);

Unit-testing a service without the HTTP pipeline:

final service = OrderService()
..attach(RequestContext(
requestId: 'test',
request: Request('POST', Uri.parse('http://localhost/orders')),
principal: const Principal(id: 'u1'),
));

See also: Services and dependency scopes.

typedef ServiceFactory<S extends Service> = S Function();

The factory passed to GisilaApp.registerService: any zero-argument constructor tear-off (UserService.new) or closure. The closure runs per request, after init(), so it may read factory singletons with app.singleton<T>().