Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5812166
#219: created the root documentation file
petermasking May 9, 2024
4e35db8
#219: fixed typos
petermasking May 9, 2024
323c272
#219: added assets docs
petermasking May 9, 2024
d96e8f0
#219: moved the tack stack below the architecture
petermasking May 9, 2024
cc233f0
#219: first draft of the Web UI docs
petermasking May 10, 2024
b563c7b
#219: empty design system message
petermasking May 10, 2024
9dec346
#219: introduced the johnDoe object
petermasking May 10, 2024
cd468dd
#219: some web ui improvements
petermasking May 10, 2024
e9b8978
#219: first draft of the domain docs
petermasking May 13, 2024
be78015
Merge branch 'main' into 219-set-up-docs
petermasking May 31, 2024
790afda
Merge branch 'main' into 219-set-up-docs
petermasking Jun 5, 2024
720a48a
Merge branch 'main' into 219-set-up-docs
petermasking Feb 10, 2025
9664606
#219: updated readme
petermasking Feb 10, 2025
2ed43a9
#219: updated the webui readme
petermasking Feb 10, 2025
070bf2b
#219: updated the domain readme
petermasking Feb 11, 2025
b872be3
#219: added integrations readme
petermasking Feb 12, 2025
8ef1e45
#219: added authentication integration docs
petermasking Feb 12, 2025
60da063
#219: added database integration docs
petermasking Feb 12, 2025
ec4ebff
#219: added event store integration docs
petermasking Feb 12, 2025
a55f7db
#219: added file store integration docs
petermasking Feb 12, 2025
f7024a7
#219: added HTTP integration docs
petermasking Feb 12, 2025
c3d9df8
#219: added logging integration docs
petermasking Feb 12, 2025
03fecf0
#219: added notification integration docs
petermasking Feb 13, 2025
43f198d
#219: added validation integration docs
petermasking Feb 13, 2025
039bee7
#219: added runtime integration docs
petermasking Feb 13, 2025
30c2cc7
#219: added additional info to integrations
petermasking Feb 13, 2025
44d7f39
#219: final touches
petermasking Feb 14, 2025
10f945b
#219: processed feedback
petermasking Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

# Comify docs

Hi, welcome to the Comify documentation. 👋

Here you'll find all the technical details on how we are building the application. We'll start with the fundamentals below. From there you can zoom in at any part for the details.

## Architecture

At the heart of any application lies architecture. To explain ours, we found inspiration from the [C4 model](https://c4model.com/) and added our own sauce.

### Context

Comify runs as a stand-alone application and hasn't many external dependencies (yet?). The most important one is the `Creator` that represents the end-user. Without it, the application doesn't have any value. Secondly we're outsourcing the identity and access management (IAM) to save on development that doesn't add business value.

<img width="1102" alt="Application context" src="./assets/images/architecture/context.png">

**Note:** We use OpenID Connect for the IAM integration.

### Shadows

The application has two shadows. Each shadow contains content related to the application.

<img width="1102" alt="Application shadows" src="./assets/images/architecture/shadows.png">

If you're reading this, then you've already found the docs. This is the darkest shadow, because it's the furthest away from the code. The tests are more code related, and can be found in the `test` folder in the project root. The application itself is placed in the `src` folder also in the project root.

The structure of the shadows mimic the application structure to make navigating them easy. This structure is explained in the containers section below.

### Containers

The application is divided into multiple parts, called containers. We have these four.

<img width="1102" alt="Application containers" src="./assets/images/architecture/containers.png">

Let's look at their usages and responsibilities.

* [**Web UI**](./webui/) - Contains the web based interface for user interaction (SPA).
* [**Assets**](./assets/) - Contains shared things like images, downloads, etc...
* [**Domain**](./domain/) - Contains business related components like business logic, data entities, etc..
* [**Integrations**](./integrations/) - Contains implementations for IAM, database, etc..

Each part has its own folder in the application (and its shadows). For more detailed information you can click on their names.

## Technology stack

The project is 100% [TypeScript](https://www.typescriptlang.org/) based.

**Runtime requirements**

* [Node.js](https://nodejs.org/) as runtime (version 20+).
* [Docker](https://www.docker.com/) for additional services.

**Main dependencies**

* [Jitar](http://jitar.dev) for E2E communication and scaling concerns.
* [React](https://react.dev) for the frontend.

All backend components are written in pure TypeScript without a framework.

**Additional services**

* [MongoDB](https://www.mongodb.com/) as database.
* [MinIO](https://min.io/) as file store (images).
* [Keycloak](https://www.keycloak.org/) as identity and access manager.

## Configuration

The application has several configuration options. You can find them here.

### Environment

For both the application and Docker services we've set up environment variables that can be managed from a single configuration file. An example of this file is located in the root of the project with the name `example.env`.

**Note:** The project uses the `development.env` file for development.

### Segments

The domain container can be split into multiple segments for scalability reasons like deployment, load balancing and fail over. This is a feature provided by Jitar, more information on this can be found in the [documentation](https://docs.jitar.dev/deploy/segmentation.html). The configuration files can be found in the `segments` folder in the project root.

We use a backend for frontend (BFF) segmentation strategy. The `bff.json` file exposes all functions that can be remotely accessed by the frontend. The other segment files are used to demonstrate two deployment strategies:

1. vertical slicing (`notification.json`)
1. horizontal slicing (`reads.json` and `writes.json`)

Based on this segmentation model, the application can run in a monolithic and microservice fashion.

### Resources

The application uses resources like a database, file store, event broker, etc. that need to be defined in Jitar for sharing them across segments that are running on the same node. More information on this can be found in the [documentation](https://docs.jitar.dev/deploy/resources.html). The configuration files can be found in the `resources` folder in the project root.

### Services

Jitar provides [multiple services](https://docs.jitar.dev/fundamentals/runtime-services.html) to deploy applications with different strategies. Currently, we have configurations for running the application as a monolith and as microservices. The configuration files can de found in the `services` folder in the project root.

The `standalone.json` configuration file configures the monolithic deployment. All other files configure the microservice setup.

## Getting started

Before you start, make sure you meet all the runtime requirements. Follow these instructions next.

1. Run `npm install` from a terminal to install the dependencies.
1. Copy the `example.env` file to a `development.env` file.
1. Run `npm run build` to build the application.
1. Run `npm run standalone` to run Jitar at port 3000.

To start the application you need to open a new terminal and run `npm run dev`. Now a [Vite](https://vitest.dev/) dev server is running on port 5173. You can then navigate to <http://localhost:5173> to open the application.

To stop the application you can press CONTROL+C in both terminals.

### Build & run

To build and run the application you can keep using the `npm run build` and `npm run standalone` commands. For convenience both commands are combined in the following command: `npm run rebuild`.

### Test & lint

To run the tests you can use the `npm run test` command. You can also run the linter with the `npm run lint` command. For convenience the **build**, **test** and **lint** commands are combined in the following command: `npm run review`.

### Run without Vite

If you want to run the application without a Vite dev server, you can change the port number in the `AUTHENTICATION_CLIENT_URI` environment variable to `3000`. After that you can start the application again as standalone and navigate to <http://localhost:3000> to open the application.

### Run as microservices

For running the application as microservices, you need to start some additional helper services:

* Proxy - central access point for the web browser.
* Repository - provider for static assets.
* Gateway - locator and load balancer for all procedures.

After that, the segment services can be started. You can follow these instructions:

1. Start the proxy by running `npm run proxy`.
1. Start the repository by running `npm run repository`.
1. Start the gateway by running `npm run gateway`.
1. Start the BFF service by running `npm run bff`.
1. Start the Notification service by running `npm run notification`.
1. Start the Reads service by running `npm run reads`.
1. Start the Writes service by running `npm run writes`.

You can then navigate to <http://localhost:3000> to open the application via the proxy service.
6 changes: 6 additions & 0 deletions docs/assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

# Assets | Comify docs

The assets container contains all kind of non-code related things that can be use by the [Web UI](../webui/) and / or the [Domain](../domain/) containers. Currently it only holds the application icon, but sooner or later it will be accompanied by other files like the *privacy statement* and the *terms of use* documents.

This shadow container serves the same goal. It contains all the images used for the documentation.
Binary file added docs/assets/images/architecture/containers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/architecture/context.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/architecture/shadows.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/domain/modules.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/webui/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/webui/modules.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions docs/domain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

# Domain | Comify docs

The domain contains the implementation of all business related components. It leverages a procedural approach using [Jitar](https://jitar.dev).

## Module structure

We're using a feature-oriented approach. Our root module structure looks like this.

<img width="1102" alt="Web UI module structure" src="../assets/images/domain/modules.png">

Basically, it boils down to a root module per domain concept. Each module contains its features.

A module is implemented as a folder containing an index file that exposes its essentials for consumers.

## Features

A feature is a sub-module containing all components required for its implementation. Features vary in size, ranging from small (single components) to large (multiple components), depending on the requirements.

Every feature exposes its main function as the default export through the index file. Additionally, its types, errors, etc., can be exposed if their consumers require them.

We aim to keep features as autonomous as possible by encapsulating their internal components. If another feature requires the same component (such as retrieving data by ID), we tend to make a copy on the first occurrence and move the component to its own feature upon multiple occurrences. This decision heavily depends on the complexity of the component.

## Components

Components can have different types depending on their responsibilities. Currently, we distinct the following types:

* **Process** - a series of steps, where each step is a separate component.
* **Task** - handles calculations, data manipulations, etc.
* **Persistence** - responsible for querying, inserting, and updating the database.
* **Aggregation** - gathers all related data.
* **Validation** - ensures incoming data meets the required criteria.
* **Event** - handles publications and subscriptions.

Each component is implemented in its own file and exported as the default.

A feature typically contains a combination of these component types. Process components that perform multiple database writes follow the [SAGA pattern](https://microservices.io/patterns/data/saga.html) as described in the [Jitar documentation](https://docs.jitar.dev/develop/data-consistency.html).

## Data

Data is defined per domain concept. We distinguish between two types:

* **Persistent** - data stored in the database.
* **Aggregated** - a data view containing all referenced data.

Both types are defined as TypeScript types with read-only fields to ensure immutability.

Persistent data is defined in the `types.ts` file in the root of the domain concept folder, as it is used by multiple features. Aggregated data is defined in the `types.ts` file of the aggregation feature, as it is more specific.

Persistent data is read and written by persistence components using the database integration.

## Events

We use a publish/subscribe model for side effects such as updating counters, creating notifications, etc.

Features that trigger side effects contain both a **publish** and **subscribe** component. Both components use the event broker integration to manage events.

The publish component is used as the last step in the process. The subscribe component is exported in the feature's index file for use by other features.

Features leverage a **subscriptions** component that imports and uses the subscribe components of other features. The subscriptions component is also exported in the feature's index file.

## Definitions

Every module (domain, concept, and feature) can have two types of definitions:

* **Types** - TypeScript types such as data and event definitions, defined in the `types.ts` file.
* **Definitions** - constants such as enums, validation schemas, record types, etc., defined in the `definitions.ts` file.

The location of a definition depends on its scope of use. For example, the base data model is located in the domain root module, a record type is defined per domain concept, and a validation schema is defined per validation feature.
109 changes: 109 additions & 0 deletions docs/integrations/AUTHENTICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

# Authentication | Comify docs

The authentication integration provides a universal interaction layer with an actual identity provider solution.

This integration is based on the following authentication flow:

1. Browser redirects to the IDP login page.
2. User authenticate at the IDP.
3. IDP provides identity information to this integration.
4. This integration starts a session based on the provided identity.
5. Sessions can be refreshed via this integration.
6. Until the user logs out via this integration.

## Implementations

Currently, there is only one implementation:

* **OpenID** - persistent document storage (used in production).

## Configuration

The used implementation needs to be configured in the `.env` file, together with the client URL.

```env
AUTHENTICATION_IMPLEMENTATION="openid"
AUTHENTICATION_CLIENT_URI="http://localhost:5173/identify"
```

In case of OpenID, additional configuration is required.

```env
OPENID_ISSUER="http://localhost:8080/realms/comify"
OPENID_CLIENT_ID="openid"
OPENID_CLIENT_SECRET=""
OPENID_REDIRECT_URI="http://localhost:3000/rpc/domain/authentication/login"
OPENID_ALLOW_INSECURE_REQUESTS=true
```

## How to use

An instance of the configured identity provider implementation can be imported for performing authentication operations.

```ts
import identityProvider from '^/integrations/authentication';

// Perform operations with the identityProvider instance
```

### Operations

```ts
import identityProvider, { Session } from '^/integrations/authentication';

// Open connection
await identityProvider.connect();

// Close connection
await identityProvider.disconnect();

// Request the URL of the login page
const loginUrl: string = await identityProvider.getLoginUrl();

// Handle a login and get a session
// Throws LoginFailed if not successful
const firstSession: Session = await identityProvider.login(providedIdentity);

// Refresh a session
// Throws LoginFailed if not successful
const secondSession: Session = await identityProvider.refresh(firstSession);

// Logout
await identityProvider.logout(secondSession);
```

### Session structure

The session has the following structure.

```ts
type Session = {
key?: string;
requester?: unknown;
identity: Identity;
accessToken: Token;
refreshToken: Token;
expires: Date;
};
```

Every session has a unique key that will be provided to external clients. This key is an unrelated hash value that contains no session information. It's ony used for referencing and storage.

The requester represents the actual logged in user retrieved from the identity information (email), and can be stored in the session for quick reference. The full Identity structure looks like this.

```ts
type Identity = {
name: string;
nickname: string | undefined;
picture: string | undefined;
email: string;
email_verified: boolean;
};
```

The access and refresh tokens can be of any type, but is always represented as string. This depends on the configured implementation. In most cases this will be a JWT.

```ts
type Token = string;
```
Loading
Loading