Skip to content

Commit

Permalink
feat(runtime): add support for setting up and tearing down applicatio…
Browse files Browse the repository at this point in the history
…ns (#390)
  • Loading branch information
petermasking authored Dec 6, 2023
1 parent a102f5b commit e1f3102
Show file tree
Hide file tree
Showing 94 changed files with 616 additions and 264 deletions.
1 change: 1 addition & 0 deletions documentation/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default defineConfig({
{ text: 'Error handling', link: '/develop/error-handling' },
{ text: 'State management', link: '/develop/state-management' },
{ text: 'Data consistency', link: '/develop/data-consistency' },
{ text: 'Set up and tear down', link: '/develop/setup-and-teardown' },
{ text: 'Middleware', link: '/develop/middleware' },
{ text: 'Validation', link: '/develop/validation' },
{ text: 'Security', link: '/develop/security' },
Expand Down
18 changes: 9 additions & 9 deletions documentation/docs/deploy/segmentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Segments are used to break applications down into distributable pieces. A segmen

```json
{
"./shared/sayHello":
"./domain/sayHello":
{
"sayHello": { "access": "public" }
}
}
```

This example includes the `sayHello` function from the `shared/sayHello.ts` module file. Now let's decompose the configuration file and dive into the details.
This example includes the `sayHello` function from the `domain/sayHello.ts` module file. Now let's decompose the configuration file and dive into the details.

### Naming and placement

Expand Down Expand Up @@ -68,7 +68,7 @@ The import names must correspond with the export names in the module. You can al

```json
{
"./shared/sayHello":
"./domain/sayHello":
{
"default": { "access": "public" },
"another": { "access": "public" },
Expand All @@ -86,7 +86,7 @@ Segments enable deploying application pieces on different servers. This requires

```json
{
"./shared/secret":
"./domain/secret":
{
"getSecret": { "access": "private" },
"useSecret": { "access": "public" }
Expand All @@ -107,7 +107,7 @@ Jitar generates an endpoint for each public function. These endpoints are used f
For example, if we need to update our sayHello function to split the name parameter into a separate first and last parameter, we can implement it like this.

```ts
// src/shared/sayHelloV2.ts
// src/domain/sayHelloV2.ts
export async function sayHello(first: string, last: string): Promise<string>
{
return `Hello, ${first} ${last}!`;
Expand All @@ -118,11 +118,11 @@ Now we have a separate module file per version that can be registered in the seg

```json
{
"./shared/sayHello":
"./domain/sayHello":
{
"sayHello": { "access": "public", "version": "1.0.0" }
},
"./shared/sayHelloV2":
"./domain/sayHelloV2":
{
"sayHello": { "access": "public", "version": "2.0.0" }
}
Expand All @@ -140,7 +140,7 @@ When registering the same version multiple times, Jitar will execute the first r
By default the import names are used for registering functions. In some cases you might want to expose a function under an alias name. A common use case is to create unique names in case multiple modules have equal exports or when combining multiple versions in a single module file. For example combining the sayHello functions.

```ts
// src/shared/sayHello.ts
// src/domain/sayHello.ts
export async function sayHello(name: string): Promise<string>
{
return `Hello, ${name}!`;
Expand All @@ -156,7 +156,7 @@ In this case both versions are in the same file, but to make them unique they bo

```json
{
"./shared/sayHello":
"./domain/sayHello":
{
"sayHello": { "access": "public", "version": "1.0.0" },
"sayHelloV2": { "access": "public", "version": "2.0.0", "as": "sayHello" }
Expand Down
79 changes: 47 additions & 32 deletions documentation/docs/develop/application-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,60 +38,75 @@ Depending on the frameworks you're using you might need to add more folders. For

## Source

The `src` folder contains all application code. Our typical structure looks like this.
The `src` folder contains all application code. Our typical (full-stack) structure looks like this.

```txt
src
├─ concept 1
│ ├─ function1.ts
│ ├─ function2.ts
│ └─ model1.ts
├─ concept 2
│ ├─ function3_v1.ts
│ └─ function3_v2.ts
├─ domain
│ ├─ concept1
│ ├─ concept2
│ ├─ ...
├─ webui
│ ├─ components
│ ├─ layouts
│ ├─ pages
├─ assets
│ ├─ downloads
│ ├─ images
│ ├─ ...
├─ integrations
│ ├─ database
│ ├─ notifications
│ ├─ ...
└─ jitar.ts
```

We use the following rules for this structure:
Each folder has it's own responsibility:

* domain - contains all business domain logic;
* webui - contains all web ui elements;
* assets - contains all assets used by the domain and webui;
* integrations - contains all integrations with external systems.

* folder per concept - we prefer using business concepts like 'account' or 'company';
* [function](../fundamentals/building-blocks#functions) per file - we use corresponding filenames with the function name like 'searchAccounts'' or 'createMonthReport';
For maintainability reasons it's important to get the dependencies right. We use the following rules:

* domain - depends on assets and integrations;
* webui - depends on assets, domain and integrations.

For setting up the domain, we use the following rules:

* folder per concept - we prefer using business domain concepts like 'account' or 'company';
* [function](../fundamentals/building-blocks#functions) per file - we use corresponding filenames with the function name like 'searchAccounts' or 'createMonthReport';
* [data model](./data-sharing) per file - use corresponding filenames with the model names like 'Account' or 'MonthReport';
* [version](../deploy/segmentation#versioning) per file - both functions and models, we add the version number at the end of the filename.

A concept folder can be split into multiple subfolders. We do this for larger applications, but we always try to keep the structure as flat as possible to avoid complexity.

For applications with a frontend we like to separate the frontend components from the rest. This makes migrating to another (version of the) framework less painful for applications that outlive their framework (we've been there). In this case we commonly use the following structure.
## Tests

The `test` folder contains all application tests. We always mimic the source folder structure here.

```txt
src
├─ shared
test
├─ domain
│ ├─ concept1
│ ├─ concept2
├─ frontend
│ ├─ ...
├─ webui
│ ├─ components
│ ├─ layouts
│ ├─ pages
├─ assets
│ ├─ downloads
│ ├─ images
│ ├─ ...
├─ integrations
│ ├─ database
│ ├─ notifications
│ ├─ ...
└─ jitar.ts
```

Make sure the frontend components import the shared components, and not the other way around.

## Tests

The `test` folder contains all application tests. We always mimic the source folder structure here.

```txt
test
├─ concept 1
│ ├─ function1.spec.ts
│ ├─ function2.spec.ts
│ └─ model1.ts
└─ concept 2
├─ function3_v1.spec.ts
└─ function3_v2.spec.ts
```

By keeping a 1-on-1 relation with the source files makes it easy to find the associated tests.

## Segments
Expand Down
4 changes: 2 additions & 2 deletions documentation/docs/develop/data-consistency.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ prev:
link: /develop/state-management

next:
text: Middleware
link: /develop/middleware
text: Set up and tear down
link: /develop/setup-and-teardown

---

Expand Down
4 changes: 2 additions & 2 deletions documentation/docs/develop/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export DatabaseError extends Error
```

```ts
// src/account/storeAccount.ts
// src/domain/account/storeAccount.ts
import { Account } from './Account';
import { DatabaseError } from '../DatabaseError';

Expand All @@ -40,7 +40,7 @@ export async function storeAccount(account: Account): Promise<void>
```

```ts
// src/account/createAccount.ts
// src/domain/account/createAccount.ts
import { Account } from './Account';
import { storeAccount } from './storeAccount';
import { DatabaseError } from '../DatabaseError';
Expand Down
4 changes: 2 additions & 2 deletions documentation/docs/develop/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
layout: doc

prev:
text: Data consistency
link: /develop/data-consistency
text: Set up and tear down
link: /develop/setup-and-teardown

next:
text: Validation
Expand Down
63 changes: 63 additions & 0 deletions documentation/docs/develop/setup-and-teardown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
layout: doc

prev:
text: Data consistency
link: /develop/data-consistency

next:
text: Middleware
link: /develop/middleware

---

# Set up and tear down

Before an application starts or stops, you might want to do some additional things like connecting and disconnecting the database.
For this, Jitar provides hooks for executing set up and tear down scripts.

The scripts can be configured per service in its configuration.
Adding both scripts to a standalone configuration looks like this:

```json
{
"url": "http://standalone.example.com:3000",
"setUp": "./setUp",
"tearDown": "./tearDown",
"standalone": {}
}
```

Both script are optional, so you are free to use the one or the other, or none at all.

The scripts do not have any specific requirements, so there's nothing special about them.
The following example shows a simple set up script.

```ts
// src/setUp.ts
import { Database } from './integrations/database/Database';

await Database.connect(process.env.DB_CONN_STRING);
```

All it does is importing dependencies and performing all actions required.

::: warning IMPORTANT
The set up script is executed before the service starts. If the script fails, Jitar will exit.
:::

The tear down script looks almost the same in this case.

```ts
// src/tearDown.ts
import { Database } from './integrations/database/Database';

await Database.disconnect();
```

::: warning IMPORTANT
The tear up script is executed after the service has stopped. If the script fails, Jitar will exit.
:::

It's common to create service specific scripts.
If the scripts overlap or get to big, we recommend breaking them up into multiple smaller scripts.
4 changes: 2 additions & 2 deletions documentation/docs/develop/writing-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ This example works with the default Jitar setup, but will break when using [midd
Functions can be imported and called as if they are locally available.

```ts
// src/shared/example.ts
// src/domain/example.ts
import { sayHello } from './sayHello';

export async function example(): Promise<void>
Expand All @@ -95,7 +95,7 @@ export async function example(): Promise<void>
console.log(message);
}

// src/shared/sayHello.ts
// src/domain/sayHello.ts
export async function sayHello(name: string): Promise<string>
{
return `Hello, ${name}!`;
Expand Down
8 changes: 4 additions & 4 deletions documentation/docs/fundamentals/building-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ In this section you'll learn about using functions and creating segments to crea
Plain functions are used as primary building blocks for applications. Let's see how a simple function looks like.

```ts
// src/shared/sayHello.ts
// src/domain/sayHello.ts
export async function sayHello(name: string): Promise<string>
{
return `Hello, ${name}!`;
Expand Down Expand Up @@ -53,7 +53,7 @@ Every function has a unique name used for internal and external identification.
{ location relative to the source folder }/{ function name}
```

For the simple sayHello function the FQN of this function is `shared/sayHello`. Note that there is no leading / in the name.
For the simple sayHello function the FQN of this function is `domain/sayHello`. Note that there is no leading / in the name.

## Segments

Expand All @@ -64,7 +64,7 @@ For the definition of a segment, JSON files are used with the '.segment.json' ex
```json
// default.segment.json
{
"./shared/sayHello":
"./domain/sayHello":
{
"sayHello":
{
Expand All @@ -85,7 +85,7 @@ This configuration connects very well with the JavaScript module system. It incl
1. Version number per function (optional, default 0.0.0)
1. Alternative name (optional, default the name of the function)

The example configuration exposes the `sayHello` function from the `./shared/sayHello` module file. The function has public access, meaning that it's accessible from other segments. Both the version and as properties have the default value, so these can optionally be removed.
The example configuration exposes the `sayHello` function from the `./domain/sayHello` module file. The function has public access, meaning that it's accessible from other segments. Both the version and as properties have the default value, so these can optionally be removed.

More in depth information on segments and the configuration can be found in the [DEPLOY section](../deploy/segmentation).

Expand Down
9 changes: 9 additions & 0 deletions documentation/docs/fundamentals/runtime-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Configurations are placed in JSON files. The basic structure looks like this.
```json
{
"url": "SERVICE_URL",
"setUp": "SET_UP_SCRIPT",
"tearDown": "TEAR_DOWN_SCRIPT",
"SERVICE_TYPE":
{
"PROPERTY_1": "",
Expand All @@ -34,6 +36,13 @@ Configurations are placed in JSON files. The basic structure looks like this.
}
```

There are four properties at root level:

* url - service url containing protocol, address and port (e.g. `http://service.example.com:3000`).
* setUp - optional [set up script](../develop/setup-and-teardown.md) that gets executed on startup (e.g. `./setUp`).
* tearDown - optional [tear down script](../develop/setup-and-teardown.md) that gets executed on shutdown (e.g. `./tearDown`).
* SERVICE_TYPE - configuration of the specific service (differs per type).

An instance can only run one type of service. Each service has its own configuration properties. All types and their properties are explained next.

## Repository
Expand Down
Loading

0 comments on commit e1f3102

Please sign in to comment.