Giter VIP home page Giter VIP logo

be-pkg-db's Introduction

Diia

This repository provides an overview over the flagship product Diia developed by the Ministry of Digital Transformation of Ukraine.

Diia is an app with access to citizen’s digital documents and government services.

The application was created so that Ukrainians could interact with the state in a few clicks, without spending their time on queues and paperwork - Diia open source application will help countries, companies and communities build a foundation for long-term relationships. At the heart of these relations are openness, efficiency and humanity.

We're pleased to share the Diia project with you.

Useful Links

Topic Link Description
Ministry of Digital Transformation of Ukraine https://thedigital.gov.ua/ The Official homepage of the Ministry of Digital Transformation of Ukraine
Diia App https://diia.gov.ua/ The Official website for the Diia application

Getting Started

This repository contains the package with database connection; migration config and index sync.

Build Process

1. Clone codebase via git clone command

Example:

git clone https://github.com/diia-open-source/be-pkg-db.git pkg-db

2. Go to code base root directory

cd ./pkg-db

3. Install npm dependencies

The installation of dependencies consists of the following 2 steps:

1. Manually clone, build and link dependencies from @diia-inhouse scope

Each Diia service depends on dependencies from @diia-inhouse/<package> scope which are distributed across different repositories, are built separately, and aren't published into public npm registry.

The full list of such dependencies can be found in the target service package.json file in dependencies and devDependencies sections respectively.

Detailed instructions on how to link dependencies from @diia-inhouse/<package> scope are described in LINKDEPS.md which can be found here https://github.com/diia-open-source/diia-setup-howto/tree/main/backend

2. Install public npm dependencies and use those linked from @diia-inhouse scope

In order to install and use the linked dependencies for pkg-db the following command can be used:

$ cd ./pkg-db
$ npm link @diia-inhouse/db @diia-inhouse/redis ... @diia-inhouse/<package-name>

In case all dependencies from @diia-inhouse scope are linked, and can be resolved, you will then have a complete list of dependencies installed for the service code base.


4. Build package

In order to build the service you have to run the command npm run build inside the root directory of service code base as per:

$ cd ./pkg-db
$ npm run build

How to contribute

The Diia project welcomes contributions into this solution; please refer to the CONTRIBUTING.md file for details

Licensing

Copyright (C) Diia and all other contributors.

Licensed under the EUPL (the "License"); you may not use this file except in compliance with the License. Re-use is permitted, although not encouraged, under the EUPL, with the exception of source files that contain a different license.

You may obtain a copy of the License at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12.

Questions regarding the Diia project, the License and any re-use should be directed to [email protected].

This project incorporates third party material. In all cases the original copyright notices and the license under which these third party dependencies were provided remains as so. In relation to the Typescript dependency you should also review the Typescript Third Party Notices.

be-pkg-db's People

Contributors

diia-opensource avatar

Stargazers

Anatoliy avatar Lyubomyr Shaydariv avatar Ksardarion avatar Artem Symonenkov avatar

Watchers

 avatar

be-pkg-db's Issues

Тести не працюють

Дані рядки мають не правильний виклик.

const envService = new EnvService()

const logger = new DiiaLogger()
const envService = new EnvService()

Якщо подивитися на EnvService

https://github.com/diia-open-source/be-pkg-env/blob/80f60ba1b629c35d77586f14bf779ae97da52fee/src/services/env.ts#L22

то можна побачити що він потребує логера як параметр.

Database-related code coupled with HTTP

async onHealthCheck(): Promise<HealthCheckResult<MongoDbStatus>> {
const dbStatus: DbStatusByType = {}
for (const [type, db] of Object.entries(this.db)) {
const dbType = <DbType>type
dbStatus[dbType] = this.dbStateCodeToName[db.connection.readyState]
}
const status = Object.values(dbStatus).some((s) => s !== DbConnectionStatus.Connected)
? HttpStatusCode.SERVICE_UNAVAILABLE
: HttpStatusCode.OK
return {
status,
details: { mongodb: dbStatus },
}
}

Database-related code should not be coupled with HTTP-related codes. This is an architectural question, where is isolation, where is layers? I'd propose to read about "Clean architecture" before starting such an important infrastructure projects.

Async init of DatabaseService

async onInit(): Promise<void> {
const tasks = Object.entries(this.dbConfigs).map(async ([type, config]) => {
const dbType = <DbType>type
this.db[dbType] = await this.createDbConnection(dbType, config)
})
await Promise.all(tasks)
const mainConfig = this.dbConfigs[DbType.Main]
if (mainConfig?.indexes?.sync) {
await this.syncIndexes(mainConfig.indexes.exitAfterSync)
}
}

Array tasks will be array of empty (undefined) promises, it makes logic of .map unclear, it is recommended to return something from map callback. The fact that Promise resolved/rejected with undefined value will return Promise is not obvious.

Also it is better to use Promise.allSettled instead of Promise.all this will detect all init errors by one start of the server app.

Respect GRASP Information expert principle

async createDbConnection(type: DbType, config: AppDbConfig): Promise<AppDb | undefined> {
const { isEnabled } = config
if (typeof isEnabled === 'boolean' && !isEnabled) {

Config validation is not a responsibility of DatabaseService class, it should be split into two tasks: validation (json-schema or other validation strategy) and config validation (responsibility of config loader). Read about GRASP Information expert principle

Decompose and error handling due to Single responsibility principle

async createDbConnection(type: DbType, config: AppDbConfig): Promise<AppDb | undefined> {
const { isEnabled } = config
if (typeof isEnabled === 'boolean' && !isEnabled) {
this.logger.info(`Database is disabled: ${type}`)
return
}
try {
const { host, port, database, authSource, user, password, replicaSet, replicaSetNodes, readPreference } = config
const connectionOptions: mongoose.ConnectOptions = {}
let hosts: string[] = []
if (host) {
if (port) {
hosts.push(`${host}:${port}`)
} else {
hosts.push(`${host}`)
}
}
if (user && password) {
connectionOptions.auth = { username: user, password }
}
if (replicaSet) {
connectionOptions.replicaSet = replicaSet
}
if (replicaSetNodes) {
if (host) {
const errMsg = 'Must be only `host` and `port` or `replicaSetNodes` config'
this.logger.error('Wrong database configuration:', errMsg)
throw new Error(errMsg)
}
hosts = replicaSetNodes.map(({ replicaHost }) => `${replicaHost}:${port}`)
}
let connectionString = `mongodb://${hosts.join(',')}/`
if (database) {
connectionOptions.dbName = database
}
const query: string[] = []
if (authSource) {
query.push(`authSource=${authSource}`)
}
if (readPreference) {
query.push(`readPreference=${readPreference}`)
}
if (query.length) {
connectionString += `?${query.join('&')}`
}
const logOptions = cloneDeep(connectionOptions)
if (logOptions.auth) {
logOptions.auth = {
username: '********',
password: '********',
}
}
this.logger.info(`Connecting to DB ${connectionString} ${type}`, logOptions)
if (this.envService.isLocal() || this.envService.isTest()) {
this.logger.debug('Mongoose set to Debug')
mongoose.set('debug', (coll, method, dbQuery, doc, options) => {
this.logger.debug('Mongo: ', { coll, method, query: dbQuery, doc, options })
})
}
let connection: mongoose.Connection
if (type === DbType.Main) {
await mongoose.connect(connectionString, connectionOptions)
connection = mongoose.connection
} else {
connection = await mongoose.createConnection(connectionString, connectionOptions).asPromise()
}
connection.on('error', (err) => {
this.logger.error('Mongo connection error', { err, type })
})
return { connection, connectionString, connectionOptions }
} catch (err) {
this.logger.error('Failed to connect to Database', { type, err })
throw new DatabaseError('Failed to connect to Database')
}
}

  1. Long function
  2. Multiple responsibilities in one place
  3. Everything on one try/catch
  4. Mixing multiple styles of error handling: on('error'... inside try/catch
  5. Mixing DatabaseError with Error, no error handling strategy at all

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.