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

feat: persisting the database #158

wants to merge 19 commits into from

Conversation

artursudnik
Copy link
Member

This PR adds:

  • support for both SQLite persisted in file and Postgres.
  • automating migration files creation and execution for both DB engines
  • configuration options to adjust TypeORM settings adequate for running VC-API service in development or production
  • keeps default settings so that already deployed instances need no adjustments to keep running with in-memory database
  • adds docker-compose.dev.yaml to start developing locally easier

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
EXPOSE 3000

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

ENV DB_TYPE=SQLITE_INMEMORY
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable is also set in .env. Can we skip it here?

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea is to have this change backward compatible with previous deployments of the SSI. Now, they use an in-memory SQLite database. This line ensures that if a container is started without DB_TYPE set, it executes as before.

apps/vc-api/docker-compose.dev.yaml Outdated Show resolved Hide resolved
@@ -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.

}
};

export const typeOrmInMemoryModuleFactory = () =>
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove this now, since sqlite config is returned by typeOrmConfigFactory

Copy link
Member Author

Choose a reason for hiding this comment

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

It is still used in tests and only there as they are executed with an in-memory database.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, why tests can't use typeOrmConfigFactory(config of sqlite datasource) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

It was just simpler for me to keep this as a wrapper than mocking ConfigService and injecting it in tests. Do you think it is worth the effort to do this?

Copy link
Member Author

Choose a reason for hiding this comment

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

it would look like this, what do you think?

        TypeOrmModule.forRoot(
          typeOrmConfigFactory({
            get: (key): Record<string, unknown> => {
              return { DB_TYPE: DB_TYPES.SQLITE_IN_MEMORY }[key];
            }
          } as unknown as ConfigService)
        ),

instead of this:

typeOrmInMemoryModuleFactory(),

apps/vc-api/src/config/env-vars-validation-schema.ts Outdated Show resolved Hide resolved
apps/vc-api/src/config/env-vars-validation-schema.ts Outdated Show resolved Hide resolved
}
]
})
.when('.DB_TYPE', {
Copy link
Contributor

Choose a reason for hiding this comment

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

why another clause of .when? Can we add is in previouse when.switch?

Copy link
Member Author

Choose a reason for hiding this comment

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

I needed to use this hack as setting common attributes for is: Joi.required().valid(DB_TYPES.SQLITE, DB_TYPES.POSTGRES) in the switch is not possible. It triggers some inconsistent behavior of Joi.

Maybe, repeating this twice would be more readable? What do you think?

DB_DROP_ON_START: Joi.boolean().default(false),
DB_SYNCHRONIZE: Joi.boolean().default(false),
DB_RUN_MIGRATIONS: Joi.boolean().default(true)

Copy link
Member Author

Choose a reason for hiding this comment

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

So, it would look like this, but we would have repeated code:

.when('.DB_TYPE', {
    switch: [
      {
        is: Joi.required().valid(DB_TYPES.SQLITE),
        then: {
          SQLITE_FILE: Joi.string().required(),
          DB_DROP_ON_START: Joi.boolean().default(false),
          DB_SYNCHRONIZE: Joi.boolean().default(false),
          DB_RUN_MIGRATIONS: Joi.boolean().default(true)
        }
      },
      {
        is: Joi.required().valid(DB_TYPES.POSTGRES),
        then: {
          POSTGRES_DB_HOST: Joi.string().hostname().required(),
          POSTGRES_DB_PORT: Joi.number().port().required(),
          POSTGRES_DB_USER: Joi.string().required(),
          POSTGRES_DB_PASSWORD: Joi.string().required(),
          POSTGRES_DB_NAME: Joi.string().required(),
          DB_DROP_ON_START: Joi.boolean().default(false),
          DB_SYNCHRONIZE: Joi.boolean().default(false),
          DB_RUN_MIGRATIONS: Joi.boolean().default(true)
        }
      }
    ]
  })

Copy link
Contributor

Choose a reason for hiding this comment

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

If these variables independent from DB_TYPE can we validate them outside of when(DB_TYPE?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are dependent on the DB_TYPE. They are required for Postgres and SQLite, but not for in-memory SQLite

Copy link
Contributor

Choose a reason for hiding this comment

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

Then may be add .with(DB_TYPE, DP_DROP_ON_START,..)?

Copy link
Member Author

Choose a reason for hiding this comment

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

But .with would require them to be defined and optional first, but in the case of in-memory SQLite, they are ignored. I think this may be confusing for someone who will make interpretations based on this schema.

Copy link
Member Author

Choose a reason for hiding this comment

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

anyway .with here would require keys to exist for DB_TYPE defined, not set to a given value

Copy link
Collaborator

@jrhender jrhender left a comment

Choose a reason for hiding this comment

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

Looks good, some minor comments/questions

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved

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.

README.md Outdated
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

By "validate migration files", did you mean that the files should be manually checked to make sure that they look correct?

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 Yes, they need to be at least reformatted because the formatting of generated files differs from what is set for the repo. I would also execute all migrations together with new ones and then execute tests.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks. By executing tests, I guess you meant manual tests of the API?

Suggested change
6. validate migration files
6. validate migration files. This should include executing all migrations (including new ones) and then executing manually tests of the API.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I am adding a suggestion to do this for both SQLite and Postgres

@artursudnik artursudnik force-pushed the feat/persist-db branch 2 times, most recently from 51e0e49 to 7fd2be8 Compare January 17, 2023 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants