0.19.0 • Published 1 month ago

@causa/runtime v0.19.0

Weekly downloads
-
License
ISC
Repository
github
Last release
1 month ago

Causa TypeScript runtime SDK

This is the repository for the @causa/runtime Node.js package. This package exposes the core concepts of Causa to backend services implemented in TypeScript and running on top of Node.js.

This runtime is mainly aimed at containerized services relying on the NestJS framework. It provides many utilities in this regard, creating an additional layer on top of the NestJS architecture.

While this is an opinionated runtime enforcing Causa concepts and forcing some tech choices, it is cloud provider-independent. For a complementary package implementing GCP-specific features, see the @causa/runtime-google package.

🎉 Installation

The package can be added like any other to a Node.js project:

npm install @causa/runtime

✨ Features

Authentication

The way an authenticated user looks like to service is normalized as the User type, simply providing an id and possibly containing additional claims. Those claims could come from a parsed JWT for example.

A user's claims can be checked using the doesUserSatisfyClaimRequirements function. This is also exposed as the @RequireUserClaims NestJS decorator.

Errors

This package exposes some entity-related errors (e.g. EntityNotFoundError) meant to be thrown by logic processing entities. In a NestJS service, those entity errors are caught and converted to HTTP errors.

Another very useful error is the RetryableError. Any business logic can throw this error to indicate that the process that failed is worth retrying. The RetryableError can be handled differently depending on the context. For example for an event-processing function, it indicates that the processing of the event should be retried.

Events

Events are a core mechanism of Causa, as they are how state changes are logged, and how services communicate between them.

The Causa runtime provides a common Event format (TypeScript type) for events, independently of how they might be serialized and sent to a broker. It also provides an interface to be implemented by EventPublishers. However, no serialization-specific nor broker-specific code is implemented in this package.

Lock

The LockManager utility provides a locking mechanism that integrates with the Transaction and TransactionRunner concepts (see the "(Event & state) transaction" section).

The lock manager operates on an entity implementing the LockEntity interface. The manager exposes standard acquire and release methods, that are usually used as part of transactions.

While the locking mechanism of some databases could be used instead of the LockManager, other databases (like Cloud Spanner) advise against it. The benefit of the LockManager is to provide a logic that will work on top of any transaction that implements FindReplaceTransaction.

Logging

The @causa/runtime package provides an opinionated way of logging, in the form of the pino library. A default configuration is set when getDefaultLogger() is first called. This configuration can be updated using updatePinoConfiguration(). This should occur before creating the logger.

When developing a NestJS service, this same logger can be injected using the @causa/runtime/nestjs's Logger.

Testing utilities are also provided. spyOnLogger() can be used in combination with getLogged[Objects|Infos|Warnings|Errors]() to assert that some messages were indeed logged.

NestJS

All features described in this section are part of the @causa/runtime/nestjs export.

Authentication

The NestJS AuthModule imports the passport module and registers the BearerAuthGuard and UserClaimsGuard.

The former expects a passport-http-bearer Strategy to be provided as a PassportStrategy. (For example, the IdentityPlatformStrategy from the @causa/runtime-google package.) The strategy should return a User object that will be forwarded to request handlers.

The latter enables the use of the @RequireUserClaims decorator on controllers and routes.

Other decorators are available, such as the @AuthUser parameter decorator (populating a route parameter with the authenticated User), and the @Public controller and route decorator (bypassing the bearer auth guard).

Exception filter

The NestJS ExceptionFilterModule provides several exception filters, converting EntityErrors to HTTP errors and unknown errors to opaque internal server errors.

When throwing errors, the subclasses of HttpError should be preferred, as they normalize the payload of the response. HttpError can also be subclassed by services to define custom HTTP errors. The corresponding DTOs can be used to document the service's API.

Events

As previously stated, this package does not provide any broker-specific implementation. However the NestJS counterpart of the EventPublisher is defined: the @InjectEventPublisher decorator, which can be used to inject an object that conforms to the corresponding interface.

The BaseEventHandlerInterceptor class is a NestInterceptor that provides a base for an interceptor meant to be used with brokers pushing messages as HTTP requests (e.g. Pub/Sub with a push subscription).

When the @EventBody decorator is used on a route's parameter, it indicates that this route responds to HTTP requests made by a message broker pushing events. An interceptor that inherits from BaseEventHandlerInterceptor catches those requests and parses the incoming events, ensuring the route handler receives a typed event, ready to be processed.

Application factory

The createApp function can be used to initialize a NestJS application from a root "business module" to which several base modules are imported globally: ConfigModule (from @nestjs/config), LoggerModule, ValidationModule, and ExceptionFilterModule (all from this package).

Note that the AuthModule is not automatically added because it is only relevant for front-facing APIs, and requires an additional Passport strategy to work.

createApp can also be used in combination with makeTestAppFactory from @causa/runtime/nestjs/testing through the appFactory option. This allows to override some of the modules (e.g. to mock services, use temporary resources, etc).

Health check

NestJS provides the @nestjs/terminus package to implement health checks, and not much can be added generically on top. The implementation of the actual health check may depend on the tech stack for example. However the terminusModuleWithLogger module provides Terminus properly configured with the pino logger.

When implementing a health check route, it should be exposed under the HEALTHCHECK_ENDPOINT, such that only error responses are logged by pino.

The HealthCheckModule can also be used to implement the health check route. In this case, only indicators, extending the BaseHealthIndicatorService, have to be implemented.

Logging

A LoggerModule is provided (and automatically imported by the application factory), which configures nestjs-pino as the logger. The Logger exposed by @causa/runtime/nestjs can be used to inject the PinoLogger.

The logger is configured to automatically log all HTTP requests, removing the authorization header in them, and ignoring successful calls to the HEALTHCHECK_ENDPOINT.

OpenAPI

NestJS provides many decorator that helps building the OpenAPI documentation for a service. This package provides those additional decorators:

  • @ApiConstantProperty
  • @ApiErrorDescription and @ApiErrorStatusCode, to decorate subclasses of ErrorDto.
  • @ApiErrorResponses, to decorate controller methods and list the possible returned errors.

Pagination

The Causa runtime provides some pagination utilities for REST APIs, including validation and transformation decorators. Paginated endpoints are meant to be requested with (a subclass of) a PageQuery, and return a Page of items.

Validation

The ValidationModule (automatically imported by the application factory) sets up the ValidationPipe, which performs transformation and validation on input objects using class-transformer and class-validator.

Basic validation classes (IdParams and VersionedMutationQuery) are also provided, meant to be used by APIs exposing CRUD operations on entities identified by a UUID V4 and versioned using an updatedAt property.

Serialization

The JsonObjectSerializer, which implements the ObjectSerializer interface, performs JSON (de)serialization from and to a Buffer containing the JSON string. The JsonSerializationOf and JsonSerializableTo types can be used to represent JavaScript objects that have gone through JSON serialization and back (e.g. Dates being transformed into strings).

The main purpose of ObjectSerializers is to be used with an EventPublisher.

The serializeAsJavaScriptObject testing utility from @causa/runtime/testing can for example be used to compare HTTP responses with typed objects.

(Event & state) transaction

Event and state Transactions are a core concept of Causa, which provide a way to modify the state of a system and publish related events as part of a single transaction. Transactions are created by a TransactionRunner, which instantiates the underlying transaction(s) to a database and a message broker.

A state transaction can optionally implement the FindReplaceStateTransaction interface, which enables the use of the Transaction with the LockManager, VersionedEntityEventProcessor, and the VersionedEntityManager.

The BufferEventTransaction provides a simple EventTransaction which accumulates events and publishes them when the transaction commits using an underlying EventPublisher.

Validation

NestJS (and the NestJS-based utilities in this package) use class-transformer and class-validator to parse and validate input payloads. However those utilities work independently of NestJS, such as validateObject and parseObject.

Several class-validator decorators are also included.

Event processing and versioned entities

The VersionedEntity is a core Causa concept that builds on top of event and state transactions. The VersionedEntityManager provides CRUD-like functionalities on entities, with the additional benefit of publishing events corresponding to the state changes. It also supports backfilling from the entity events, and can be extended to customize the processing of each event (e.g. to build a separate view or index).

The VersionedEntityManager relies on the VersionedEntityEventProcessor, which consumes events to build views, but does not provide the create / update / delete functionalities.

The VersionedEntityManager is usually extended in the service managing the entity, while a separate service that requires building its own view on the same entity will extend a VersionedEntityEventProcessor.

0.19.0

1 month ago

0.18.1

1 month ago

0.18.0

1 month ago

0.17.0

2 months ago

0.16.0

2 months ago

0.16.1

2 months ago

0.15.0

3 months ago

0.14.0

3 months ago

0.13.1

4 months ago

0.13.0

5 months ago

0.12.0

5 months ago

0.10.0

6 months ago

0.3.0

10 months ago

0.2.0

10 months ago

0.11.0

6 months ago

0.9.0

6 months ago

0.8.0

7 months ago

0.5.0

10 months ago

0.3.2

10 months ago

0.4.0

10 months ago

0.3.1

10 months ago

0.7.0

8 months ago

0.6.1

8 months ago

0.6.0

9 months ago

0.1.0

10 months ago