Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: persisting the database #158

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
752de20
feat: implementing database config used based on the `DB_TYPE` env va…
artursudnik Dec 15, 2022
2b5644c
tests: enforcing `DB_TYPE='SQLITE_INMEMORY'` in the `vc-api` E2E tests
artursudnik Dec 16, 2022
cf48473
chore: setting docker image to use SQLITE_INMEMORY DB_TYPE by default
artursudnik Dec 16, 2022
a90ffe8
chore: extending env vars schema
artursudnik Dec 15, 2022
4aebe6c
chore: adding `.env.example`
artursudnik Dec 15, 2022
3deebd8
feat: adding file SQLite support
artursudnik Dec 15, 2022
c811785
chore: adding volume and related default `SQLITE_FILE` value to the D…
artursudnik Dec 16, 2022
d74f680
chore: adding the `docker-compose.dev.yaml` file
artursudnik Dec 16, 2022
7303dac
feat: implementing Postgres DB support
artursudnik Dec 16, 2022
ec8c40e
chore: implementing npm scripts to create and execute migrations
artursudnik Dec 16, 2022
bc8695d
chore: adding initial migrations
artursudnik Dec 16, 2022
39c098a
docs: updating the README
artursudnik Dec 19, 2022
ddc787d
chore: updating docker-compose.dev.yaml to take Postgres config from …
artursudnik Jan 9, 2023
ce5b062
chore: renaming env variable `DB_DROP_SCHEMA` -> `DB_DROP_ON_START`
artursudnik Jan 9, 2023
157d938
chore: renaming env variable `SQLITE_INMEMORY` -> `SQLITE_IN_MEMORY`
artursudnik Jan 9, 2023
d4ccf36
chore: renaming env variable `DB_SYNCHRONIZE` -> `DB_SYNC_SCHEMA_ON_S…
artursudnik Jan 9, 2023
8fa1e3e
refactor: removing the `typeOrmInMemoryModuleFactory`
artursudnik Jan 9, 2023
8639fda
docs: minor fixes of the README.md
artursudnik Jan 16, 2023
792c0a8
docs: adding remarks to the README.md
artursudnik Jan 16, 2023
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
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/Dockerfile
**/Dockerfile
docker-compose.yml
**/docker-compose.yml
.dockerignore
**/node_modules
npm-debug.log
.vscode
.next
.git
.env
**/.env
**/dist
# Rush temporary files
common/deploy/
Expand All @@ -16,3 +18,11 @@ common/autoinstallers/*/.npmrc
**/.rush/temp/

**/*.log

*.db
*.db-shm
*.db-wal

**/*.db
**/*.db-shm
**/*.db-wal
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ common/autoinstallers/*/.npmrc
dist
.vscode/
docker-compose.yml
*.db
*.db-shm
*.db-wal
160 changes: 160 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,166 @@ To run tests across all apps and libraries in one command, a rush script has bee
$ rush test
```

## Multiple database engines support
VC-API backend supports:
- in-memory SQLite (default)
- SQLite persisted in a file
- Postgres (needs to be started separately)

### In-memory SQLite
**This mode is not intended to be used by multiple load balanced VC-API service instances.**

This is the default mode of operation. The database in this mode is purged and initiated everytime process is started.
No special
steps are required when upgrading versions of the VP-API. Every instance of the VC-API uses its own instance of this
database.

Start your local VC-API instance with the following:
```shell
$ cd apps/vc-api
$ npm run start:dev
```

### SQLite
**This mode is not intended to be used by multiple load balanced VC-API service instances.**

To activate data persistence for SQLite, set the following env variables:
```dotenv
DB_TYPE=SQLITE
SQLITE_FILE=<path/to/your/file/sqlite.db>
```

Start the VC-API:
```shell
$ cd apps/vc-api
$ npm run start:dev
```

### Postgres
To use Postgres database, start your local postgres server, for example with docker and `docker-compose.full.yaml`
located in the `apps/vc-api` folder:
```shell
$ cd apps/vc-api
$ docker compose -f docker-compose.full.yaml up
```

Set the following env variables:
```dotenv
DB_TYPE=POSTGRES
POSTGRES_DB_HOST=127.0.0.1
POSTGRES_DB_PORT=5432
POSTGRES_DB_USER=postgres
POSTGRES_DB_PASSWORD=postgres
POSTGRES_DB_NAME=vc-api
```

Start the VC-API:
```shell
$ cd apps/vc-api
$ npm run start:dev
```

## Database migrations

Having database persisted on production makes it necessary to have database schema migration process in place when
upgrading VC-API version running. Thanks to the migrations feature of TypeORM, this can be automated.

### Executing migrations after VC-API code updated

#### WARNING!!!

The following settings are recommended on production. These are also default application settings if not provided:
```dotenv
# below, if set to `true`, erases all data on every application start
DB_DROP_ON_START=false
# below is intended to be set to `true` only when developing locally
DB_SYNC_SCHEMA_ON_START=false
```

Execution of the database schema migrations is automated, so that only required migrations are executed to upgrade the
database schema. This can be done in two ways:

#### By the application

A VC-API service is able to execute migrations by itself when starting. To enable this feature the following env
variable is needed:
```dotenv
DB_RUN_MIGRATIONS=true
JGiter marked this conversation as resolved.
Show resolved Hide resolved
```

#### With npm script

If you decide to disable execution of migrations by application, they can be executed manually:
```shell
$ cd apps/vc-api
$ npm run migration:run
```

### Creating new migrations after development

The reccommended settings for development are:
```dotenv
DB_DROP_ON_START=true
DB_SYNC_SCHEMA_ON_START=true
DB_RUN_MIGRATIONS=true
```

This means that database schema will be dropped on every application restart, migrations executed and all entities
changes not reflected in migrations will be synchronised to the DB schema. This allows faster development.

After completing chunk of work, for example when preparing a pull request to be reviewed and merged, another db
schema migration needs to be created and committed.

**Warning!** Two sets of migrations need to be added. For SQLite and Postgres.

To create migrations, you will need to:

1. [drop db schemas](#dropping-schemas) for both Postgres and SQLite
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artursudnik why is this step necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrhender Because during development we usually execute the service with DB_SYNC_SCHEMA_ON_START=true and before generating a new migration we need to recreate the schema to be in sync with existing migrations only. Then, TypeORM is able to calculate the delta between modified entities and the schema defined before.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, I know that isn't the first time you've explained it to me 😅
Maybe we can add the explanation to the documentation.

Suggested change
1. [drop db schemas](#dropping-schemas) for both Postgres and SQLite
1. [drop db schemas](#dropping-schemas) for both Postgres and SQLite. This is useful because, if `DB_SYNC_SCHEMA_ON_START=true` is used during development, then the db schemas needs to be in sync with existing migrations only prior to generating new ones.

2. stop your application instances if running
3. ensure your `.env` file contains valid settings for both Postgres and SQLite persisted in file
4. [execute](#executing-existing-migrations) existing migrations
5. [generate](#generating-new-migration-files) migration files
6. validate migration files - this should include executing all migrations (including new ones) and E2E tests for both
database engines SQLite and Postgres
7. commit migration files

#### Dropping schemas
1. Remove your SQLite file. By default, it should be `apps/vc-api/sqlite.db`.
2. Start your Postgres instance from scratch. If you started it with `docker compose -f docker-compose.full.yaml up`
execute the following:
```shell
$ cd apps/vc-api
$ docker compose -f docker-compose.full.yaml down -v
```
`-v` means volumes are to be removed - **this is important option here**

#### Executing existing migrations
```shell
$ npm run migration:run:pg
```
```shell
$ npm run migration:run:sqlite
```

#### Generating new migration files
```shell
npm run migration:generate:pg <migration-name>
```
this should add a new migration file for Postgres to the `apps/vc-api/src/migrations/pg` folder

and
```shell
npm run migration:generate:sqlite <migration-name>
```

this should add a new migration file for SQLite to the `apps/vc-api/src/migrations/sqlite` folder

#### Warning
Migrations are executed in order of files sorted by name. Migration files start with a timestamp, for instance
`1671212337326-initial.ts`. If you are planning to merge your PR to the base branch, keep in mind that in some
situations you will need to delete and regenerate your migrations again to keep them in order with migrations added
in meantime to the base branch.

## Contributing Guidelines
See [contributing.md](./contributing.md)

Expand Down
16 changes: 16 additions & 0 deletions apps/vc-api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DB_TYPE=SQLITE_IN_MEMORY

#DB_TYPE=SQLITE
SQLITE_FILE=sqlite.db

#DB_TYPE=POSTGRES
POSTGRES_DB_HOST=127.0.0.1
POSTGRES_DB_PORT=5432
POSTGRES_DB_USER=postgres
POSTGRES_DB_PASSWORD=postgres
POSTGRES_DB_NAME=vc-api

#for DB_TYPE=POSTGRES or DB_TYPE=SQLITE
DB_DROP_ON_START=false
DB_SYNC_SCHEMA_ON_START=false
DB_RUN_MIGRATIONS=true
6 changes: 6 additions & 0 deletions apps/vc-api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ RUN cp -R ./apps/vc-api/dist ./common/deploy/apps/vc-api

FROM base as final
ENV NODE_ENV=production

EXPOSE 3000

COPY --from=builder /app/common/deploy /app
COPY license-header.txt /app

ENV DB_TYPE=SQLITE_IN_MEMORY

VOLUME /data
ENV SQLITE_FILE=/data/sqlite.db

CMD ["node", "apps/vc-api/dist/src/main.js"]
22 changes: 22 additions & 0 deletions apps/vc-api/docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.8'

services:
postgres:
image: postgres:14-alpine
restart: unless-stopped
environment:
POSTGRES_PASSWORD: $POSTGRES_DB_PASSWORD
POSTGRES_USER: $POSTGRES_DB_USER
POSTGRES_DB: $POSTGRES_DB_NAME
ports:
- '127.0.0.1:5432:5432'
volumes:
- dev-postgres-data:/var/lib/postgresql/data
networks:
main:

networks:
main:

volumes:
dev-postgres-data:
11 changes: 9 additions & 2 deletions apps/vc-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"write-openapi": "ts-node ./scripts/write-open-api-json.ts && prettier -w docs/openapi.json",
"update-openapi": "npm run write-openapi; git add docs/openapi.json"
"update-openapi": "npm run write-openapi; git add docs/openapi.json",
"migration:generate:pg": "export DB_TYPE=POSTGRES; npm run migration:run && cd src/migrations/pg && typeorm-ts-node-commonjs -d ../../config/ormconfig.ts migration:generate -p",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - ideally to configure by .env and have single migration:generate

Copy link
Member Author

@artursudnik artursudnik Jan 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was the first approach, but after exercising the creation of migrations a few times, I created two migration scripts in the package.json because when generating migrations, it is necessary to execute both for Postgres and SQLite one after another. Editing .env every time would be really annoying and prone to mistakes. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is necessary to execute both for Postgres and SQLite

Could you give an example for such scenario?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Postgres and SQLite are different DB engines, so migration files are different for them. If we want SSI to be able to use any of them, we need to provide migrations for both of them that differ. So, when DB schema is extended during development, it is necessary to generate a new migration file for Postgres and SQLite. It is not possible to execute Postgres migration on SQLite SQLite migration on Postgres.

"migration:generate:sqlite": "export DB_TYPE=SQLITE; npm run migration:run && cd src/migrations/sqlite && typeorm-ts-node-commonjs -d ../../config/ormconfig.ts migration:generate -p",
"migration:run": "typeorm-ts-node-commonjs -d src/config/ormconfig.ts migration:run",
"migration:run:pg": "export DB_TYPE=POSTGRES; typeorm-ts-node-commonjs -d src/config/ormconfig.ts migration:run",
"migration:run:sqlite": "export DB_TYPE=SQLITE; typeorm-ts-node-commonjs -d src/config/ormconfig.ts migration:run"
},
"dependencies": {
"@energyweb/ssi-did": "0.0.1",
Expand All @@ -49,7 +54,9 @@
"@nestjs/axios": "^3.0.0",
"@nestjs/serve-static": "^4.0.0",
"joi": "^17.9.2",
"axios": "~1.4.0"
"axios": "~1.4.0",
"pg": "^8.0.0",
"dotenv": "^16.3.1"
},
"devDependencies": {
"@nestjs/cli": "^10.1.0",
Expand Down
13 changes: 10 additions & 3 deletions apps/vc-api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
*/

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { KeyModule } from './key/key.module';
import { DidModule } from './did/did.module';
import { VcApiModule } from './vc-api/vc-api.module';
import { TypeOrmSQLiteModule } from './in-memory-db';
import { typeOrmConfigFactory } from './config/db';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { SeederModule } from './seeder/seeder.module';
import { envVarsValidationSchema } from './config/env-vars-validation-schema';
import { TypeOrmModule } from '@nestjs/typeorm';

let config: DynamicModule;

Expand All @@ -46,7 +47,13 @@ try {

@Module({
imports: [
TypeOrmSQLiteModule(),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config) => ({
...typeOrmConfigFactory(config)
})
}),
KeyModule,
DidModule,
VcApiModule,
Expand Down
Loading
Loading