Skip to content

Commit

Permalink
[Init]
Browse files Browse the repository at this point in the history
  • Loading branch information
unaussprechlich committed Sep 28, 2023
1 parent d05c294 commit 2fc8c36
Show file tree
Hide file tree
Showing 60 changed files with 449 additions and 144 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center"><img alt="Logo" width="200px" height="200px" src="https://raw.githubusercontent.com/unaussprechlich/ts-messaging/dev/docs/src/public/logo.png"></p>


# ts-messaging
# ts-messaging (Status: Research Prototype)

An opinionated framework for type consistency in message stacks with a focus on developer experience.

Expand Down
62 changes: 2 additions & 60 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ export default defineConfig({
{ text: 'Contract', link: 'contract' },
{ text: 'Channel', link: 'channel' },
{ text: 'Registry', link: 'registry' },
{ text: 'Broker', link: 'broker' },
{ text: 'Controller', link: 'controller' },
{ text: 'Endpoint', link: 'endpoint' },
{ text: 'Message', link: 'message' },
],
},
{
Expand All @@ -58,28 +60,6 @@ export default defineConfig({
text: 'Overview',
link: 'overview',
},
{
text: 'Installation',
link: 'installation',
},
{
text: '@Avro.InjectSchema',
link: 'schema',
base: '/decorators/schema/',
},
{
text: '@Avro.Record',
link: 'record',
base: '/packages/schema-avro/decorators/fields/',
collapsed: true,
items: [
{ text: '@Avro.Int', link: 'int' },
{ text: '@Avro.String', link: 'string' },
{ text: '@Avro.Double', link: 'double' },
{ text: '@Avro.Boolean', link: 'double' },
{ text: '🚧 @Avro....', link: 'double' },
],
},
],
},
{
Expand All @@ -91,14 +71,6 @@ export default defineConfig({
text: 'Overview',
link: 'overview',
},
{
text: 'Installation',
link: 'installation',
},
{
text: '@Confluent.Subject',
link: '/decorators/inject-subject',
},
],
},
{
Expand All @@ -110,36 +82,6 @@ export default defineConfig({
text: 'Overview',
link: 'overview',
},
{
text: 'Installation',
link: 'installation',
},
{
text: '@Kafka.InjectClient',
link: 'schema',
base: '/decorators/inject-client',
},
{
text: '@Kafka.InjectTopic',
link: 'schema',
base: '/decorators/inject-topic',
},
{
text: '@Kafka.Controller',
link: 'schema',
base: '/decorators/controller/',
},
{
text: '@Kafka.Endpoint',
link: 'record',
base: '/packages/client-kafka/decorators/params/',
collapsed: true,
items: [
{ text: '@Kafka.Key', link: 'key' },
{ text: '@Kafka.Value', link: 'value' },
{ text: '@Avro.Meta', link: 'meta' },
],
},
],
},
],
Expand Down
87 changes: 87 additions & 0 deletions docs/src/architecture/broker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Broker

The `Broker` is the interface to the underlying message broker. It is responsible for the transmission of messages between the producer and the consumer.

## Internal Broker Architecture

Internally, the broker is defined by the `Broker`, `Producer`, and `Consumer` adapters and their broker-specific implementations.

## :package: `@ts-messaging/common`

### `Broker`

The broker interface is defined by the `createProducer` and `createConsumer` methods. The `createProducer` method is used to create a `Producer` instance and the `createConsumer` method is used to create a `Consumer` instance and automatically register them with the broker.

```ts
export interface Broker extends Connectable{
createProducer(config: unknown): Producer;
createConsumer(config: unknown): Consumer;

findChannel(name: string): Promise<Channel | null>;
}
```

### `Producer`

The sole responsibility of the `Producer` is to transmit messages to the broker. The `Producer` is created by the `Broker` and is automatically registered with the remote broker.

```ts
export interface Producer extends Connectable {
produce(message: any): Promise<unknown>;
}
```

### `Consumer`

On this highest level, the only responsibility of the `Consumer` is to subscribe to channels. The `Consumer` is created by the `Broker` and is automatically registered with the remote broker.

```ts
export interface Consumer extends Connectable {
subscribe(channels: Channel[]): Promise<void>;
}
```

## :package: `@ts-messaging/client`

### `AbstractBroker`

The `Broker` interface is extended by a caching mechanism. The `AbstractBroker` is responsible for caching the `Channel` instances.

```ts
export abstract class AbstractBroker<TChannel extends Channel> implements Broker {

protected readonly channels = new Cache<string, TChannel>((key: string) =>
this.loadChannel(key)
);

protected abstract loadChannel(topicName: string): Promise<TChannel | null>;

async findChannel(name: string): Promise<TChannel | null> {
return this.channelCache.find(name);
}

abstract createConsumer(config: unknown): Consumer;
abstract createProducer(config: unknown): Producer;
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;
}
```

### `AbstractConsumer`

The `AbstractConsumer` is responsible for the transmission of messages to the `Controller` instances. They are registered within the `AbstractConsumer` and are called when a message is received. The implementation is highly broker-specific.

```ts
export abstract class AbstractConsumer implements Consumer {
protected readonly controllers: Controller[] = [];

registerController(controller: Controller): void {
this.controllers.push(controller);
}

abstract subscribe(channels: Channel[]): Promise<void>;
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>
}
```

54 changes: 51 additions & 3 deletions docs/src/architecture/channel.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,63 @@
# Channel

The `Channel` is a resource of the `Broker`. It is a message transmission path. Each channel holds its associated contract and can use them to encode and decode the entire messages.
The `Channel` is a resource of the `Broker`. It is a message transmission path. Each channel can determine its associated contract.

## Internal Topic Architecture
## Internal Channel Architecture

Internally, the channel is defined by `Channel`, `AbstractChannel`, and their registry-specific implementations.

## :package: `@ts-messaging/common`

### `Channel`
```ts
export interface Channel {
readonly name: string;
readonly name: string;

findContract(): Promise<Contract | null>;
}
```

### `ChannelFactory`
The `ChannelFactory` is used by the `Broker` to create a `Channel` from the name of the channel. The `ChannelFactory` is broker-specific and is implemented by each client package.

```ts
export interface ChannelFactory{
produce(input: { name: string }): Promise<Channel>;
}
```

## :package: `@ts-messaging/client`

### `AbstractChannel`

The `AbstractChannel` is the base class for all broker-specific implementations of the `Channel` interface. The `AbstractChannel` is responsible for caching the contract associated with the channel.

```ts
import { Schema } from "./Schema";

export abstract class AbstractContract implements Channel {

abstract readonly name: string;
protected contract: Promise<Contract | null> | undefined;

//The broker-specific loading of the contract
protected abstract loadContract(): Promise<Contract | null>;

//Caching mechanism for the contract
async findContract(): Promise<Contract | null> {
if (this.contract) {
return this.contract;
}

this.contract = this.loadContract();
const value = await this.contract;

if (!value) {
this.contract = undefined;
}

return value;
}
}
```

16 changes: 8 additions & 8 deletions docs/src/architecture/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ Contracts are the key resource of the [Registry](/architecture/registry) and are

## Internal Contract Architecture

Internally, the channel is defined by `Contract`, `AbstractContract`, and their registry-specific implementations.
Internally, the contract is defined by `Contract`, `AbstractContract`, and their registry-specific implementations.

## :package: `@ts-messaging/common`

### `Contract<T>`
Inside a `Contract` each registered `Schema` will be assigned a unique version. The `Contract` is the combination of the `Schema` and the `Contract`.
### `ContractVersion<T>`
Inside a `ContractVersion` each registered `Schema` will be assigned a unique version. The `ContractVersion` is the combination of the `Schema` and the `Contract`.

```ts
export interface ContractVersion<T extends SchemaObject = SchemaObject>{
version: number;
schema: Schema<T>;
channel: string
};
export interface ContractVersion<T extends SchemaObject = SchemaObject> {
contract: Contract;
version: number;
schema: Schema<T>;
}
```

### `Contract`
Expand Down
31 changes: 31 additions & 0 deletions docs/src/architecture/controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Controller

The `Controller` is highly broker-specific. It is serves as a mediator and controller between endpoints and consumers.

## Internal Controller Architecture

Internally the controller is represented by `Controller`, `XXX`, `XXX`, and their broker-specific implementations.

## :package: `@ts-messaging/common`

### `Controller`

Internally, the controller is defined by the `Controller` interface. It contains a reference to its consumer and a `handleMessage` method that is invoked by the consumer when a message is received.

```ts
export interface Controller {
readonly consumer: Consumer;
handleMessage(message: Message): Promise<{
invocations: number;
}>;
}
```

### `ControllerFactory`
The `ControllerFactory` is used by the `Client` to create a `Controller` from an annotated class. The `ControllerFactory` is broker-specific and is implemented by each client package.

```ts
export interface ControllerFactory {
produce(controllerConstructor: Constructor): Promise<Controller>;
}
```
17 changes: 11 additions & 6 deletions docs/src/architecture/endpoint.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
# Endpoint

The `Endpoint` is a novel concept that is used to deliver a message to the business logic of the application. The endpoint therefore determines if a message can be committed by the consumer after it has been processed.
The `Endpoint` is a novel concept that is used to deliver a message to the business logic of the application. The endpoint therefore determines if a message can be committed by the consumer after it has been processed. As its implementation is highly broker-dependent, the `endpoint is only implemented in the client package.

## Internal Contract Architecture
## Internal Endpoint Architecture

Internally, the endpoint is defined by the `Endpoint` interface and their broker-specific implementations.

## :package: `@ts-messaging/common`

### `Endpoint`

```ts
export interface MessageEndpoint<M extends Message = Message> {
channelName: string;
schema: Record<string, number[]> | number[];
endpoint: (message: M) => Promise<void>;
export interface MessageEndpoint {
name: string;
channel: Channel;
payloadContractVersion: ContractVersion | null;
endpoint: (...args: any[]) => Promise<void>;
}
```
30 changes: 30 additions & 0 deletions docs/src/architecture/message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Message

The `Message` describes the common representation of a message inside the framework. It is used by the `Broker` to transmit messages between the producer and the consumer.

## Internal Message Architecture

Internally, the message is defined by the `Message` and `MessageData` interface and their broker-specific implementations.

## :package: `@ts-messaging/common`

### `Message`

The message interface contains the `channel`, `payload`, `contractVersion`, and `meta` properties. The `payload` is a schema object and `contractVersion` the associated version of the contract used to encode the message, and the `meta` is the broker-specific metadata.

```ts
export interface Message<T extends SchemaObject = any> {
readonly channel: Channel;
payload: T | null;
contractVersion: ContractVersion<T> | null;
meta: MessageMetadata;
}
```

### `MessageMetadata`

The `MessageMetadata` interface is a placeholder for broker-specific metadata.

```ts
export interface MessageMetadata {}
```
Loading

0 comments on commit 2fc8c36

Please sign in to comment.