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

[mds-service-helpers] Create separate ProcessController interface #340

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/mds-jurisdiction-service/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { UnwrapServiceResult, ServiceClient } from '@mds-core/mds-service-helper
import { JurisdictionServiceProvider } from '../service/provider'
import { JurisdictionService } from '../@types'

const { initialize, shutdown, ...service } = JurisdictionServiceProvider
const { start, stop, ...service } = JurisdictionServiceProvider

export const JurisdictionServiceClient: ServiceClient<JurisdictionService> = {
createJurisdiction: UnwrapServiceResult(service.createJurisdiction),
Expand Down
4 changes: 2 additions & 2 deletions packages/mds-jurisdiction-service/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
limitations under the License.
*/

import { ServiceManager } from '@mds-core/mds-service-helpers'
import { ProcessManager } from '@mds-core/mds-service-helpers'
import { JurisdictionServiceProvider } from '../service/provider'

ServiceManager.run(JurisdictionServiceProvider)
ProcessManager(JurisdictionServiceProvider).monitor()
8 changes: 4 additions & 4 deletions packages/mds-jurisdiction-service/service/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
limitations under the License.
*/

import { ServiceProvider } from '@mds-core/mds-service-helpers'
import { ServiceProvider, ProcessController } from '@mds-core/mds-service-helpers'
import { JurisdictionRepository } from './repository'
import { JurisdictionService } from '../@types'
import * as handlers from './handlers'

export const JurisdictionServiceProvider: ServiceProvider<JurisdictionService> = {
initialize: JurisdictionRepository.initialize,
shutdown: JurisdictionRepository.shutdown,
export const JurisdictionServiceProvider: ServiceProvider<JurisdictionService> & ProcessController = {
start: JurisdictionRepository.initialize,
stop: JurisdictionRepository.shutdown,
...handlers
}
7 changes: 5 additions & 2 deletions packages/mds-jurisdiction-service/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import test from 'unit.js'
import { uuid, days } from '@mds-core/mds-utils'
import { ProcessManager } from '@mds-core/mds-service-helpers'
import { JurisdictionServiceClient } from '../index'
import { JurisdictionServiceProvider } from '../service/provider'

Expand All @@ -26,9 +27,11 @@ const TODAY = Date.now()
const YESTERDAY = TODAY - days(1)
const LAST_WEEK = TODAY - days(7)

const controller = ProcessManager(JurisdictionServiceProvider).controller()

describe('Write/Read Jurisdictions', () => {
before(async () => {
await JurisdictionServiceProvider.initialize()
await controller.start()
})

it(`Write ${records} Jurisdiction${records > 1 ? 's' : ''}`, async () => {
Expand Down Expand Up @@ -228,6 +231,6 @@ describe('Write/Read Jurisdictions', () => {
})

after(async () => {
await JurisdictionServiceProvider.shutdown()
await controller.stop()
})
})
8 changes: 4 additions & 4 deletions packages/mds-jurisdiction/tests/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { uuid } from '@mds-core/mds-utils'
import { JurisdictionServiceProvider } from '@mds-core/mds-jurisdiction-service/service/provider'
import { SCOPED_AUTH } from '@mds-core/mds-test-data'
import { JurisdictionDomainModel } from '@mds-core/mds-jurisdiction-service'
import { ServiceManager } from '@mds-core/mds-service-helpers'
import { ProcessManager } from '@mds-core/mds-service-helpers'
import { api } from '../api'
import { JURISDICTION_API_DEFAULT_VERSION } from '../@types'

Expand All @@ -34,11 +34,11 @@ const [JURISDICTION0, JURISDICTION1, JURISDICTION2] = [uuid(), uuid(), uuid()].m
geography_id: uuid()
}))

const service = ServiceManager.controller(JurisdictionServiceProvider)
const controller = ProcessManager(JurisdictionServiceProvider).controller()

describe('Test Jurisdiction API', () => {
before(async () => {
await service.start()
await controller.start()
})

it('Create Single Jurisdiction', async () => {
Expand Down Expand Up @@ -214,6 +214,6 @@ describe('Test Jurisdiction API', () => {
})

after(async () => {
await service.stop()
await controller.stop()
})
})
8 changes: 5 additions & 3 deletions packages/mds-service-helpers/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export type ServiceClient<I> = {

export type ServiceProvider<I> = {
[P in keyof I]: I[P] extends AnyFunction<infer R> ? (...args: Parameters<I[P]>) => Promise<ServiceResponse<R>> : never
} & {
initialize: () => Promise<void>
shutdown: () => Promise<void>
}

export interface ProcessController {
start: () => Promise<void>
stop: () => Promise<void>
}
54 changes: 27 additions & 27 deletions packages/mds-service-helpers/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import logger from '@mds-core/mds-logger'
import { hours, minutes, seconds, NotFoundError, ValidationError, ConflictError } from '@mds-core/mds-utils'
import { Nullable } from '@mds-core/mds-types'
import retry, { Options as RetryOptions } from 'async-retry'
import { ServiceProvider, ServiceResultType, ServiceErrorDescriptor, ServiceErrorType } from '../@types'
import { ServiceResultType, ServiceErrorDescriptor, ServiceErrorType, ProcessController } from '../@types'

type ProcessMonitorOptions = Partial<
Omit<RetryOptions, 'onRetry'> & {
Expand All @@ -27,14 +27,10 @@ type ProcessMonitorOptions = Partial<
}
>

type ProcessMonitor = {
stop: () => Promise<void>
}

const ServiceMonitor = async <TServiceInterface>(
service: ServiceProvider<TServiceInterface>,
const ProcessMonitor = async (
controller: ProcessController,
options: ProcessMonitorOptions = {}
): Promise<ProcessMonitor> => {
): Promise<ProcessController> => {
const {
interval = hours(1),
signals = ['SIGINT', 'SIGTERM'],
Expand All @@ -54,8 +50,8 @@ const ServiceMonitor = async <TServiceInterface>(
try {
await retry(
async () => {
logger.info(`Initializing service ${version}`)
await service.initialize()
logger.info(`Initializing process ${version}`)
await controller.start()
},
{
retries,
Expand All @@ -66,51 +62,55 @@ const ServiceMonitor = async <TServiceInterface>(
onRetry: (error, attempt) => {
/* istanbul ignore next */
logger.error(
`Initializing service ${version} failed: ${error.message}, Retrying ${attempt} of ${retries}....`
`Initializing process ${version} failed: ${error.message}, Retrying ${attempt} of ${retries}....`
)
}
}
)
} catch (error) /* istanbul ignore next */ {
logger.error(`Initializing service ${version} failed: ${error.message}, Exiting...`)
await service.shutdown()
logger.error(`Initializing process ${version} failed: ${error.message}, Exiting...`)
await controller.stop()
process.exit(1)
}

// Keep NodeJS process alive
logger.info(`Monitoring service ${version} for ${signals.join(', ')}`)
logger.info(`Monitoring process ${version} for ${signals.join(', ')}`)
const timeout = setInterval(() => {
logger.info(`Monitoring service ${version} for ${signals.join(', ')}`)
logger.info(`Monitoring process ${version} for ${signals.join(', ')}`)
}, interval)

const shutdown = async (signal: NodeJS.Signals) => {
const terminate = async (signal: NodeJS.Signals) => {
clearInterval(timeout)
logger.info(`Terminating service ${version} on ${signal}`)
await service.shutdown()
logger.info(`Terminating process ${version} on ${signal}`)
await controller.stop()
process.exit(0)
}

// Monitor process for signals
signals.forEach(signal =>
process.on(signal, async () => {
await shutdown(signal)
await terminate(signal)
})
)

return { stop: async () => shutdown('SIGUSR1') }
return {
start: async () => undefined,
stop: async () => terminate('SIGUSR1')
}
}

export const ServiceManager = {
run: <TServiceInterface>(service: ServiceProvider<TServiceInterface>, options: ProcessMonitorOptions = {}) => {
export const ProcessManager = (controller: ProcessController, options: ProcessMonitorOptions = {}) => ({
monitor: () => {
// eslint-reason disable in this one location until top-level await
// eslint-disable-next-line @typescript-eslint/no-floating-promises
ServiceMonitor(service, options)
ProcessMonitor(controller, options)
},
controller: <TServiceInterface>(service: ServiceProvider<TServiceInterface>, options: ProcessMonitorOptions = {}) => {
let monitor: Nullable<ProcessMonitor> = null
controller: (): ProcessController => {
let monitor: Nullable<ProcessController> = null
return {
start: async () => {
if (!monitor) {
monitor = await ServiceMonitor(service, options)
monitor = await ProcessMonitor(controller, options)
}
},
stop: async () => {
Expand All @@ -121,7 +121,7 @@ export const ServiceManager = {
}
}
}
}
})

export const ServiceResult = <R>(result: R): ServiceResultType<R> => ({ error: null, result })

Expand Down
10 changes: 5 additions & 5 deletions packages/mds-service-helpers/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import test from 'unit.js'
import { ServiceResult, ServiceError, ServiceException, isServiceError, ServiceManager } from '../index'
import { ServiceResult, ServiceError, ServiceException, isServiceError, ProcessManager } from '../index'
import { UnwrapServiceResult } from '../client'

describe('Tests Service Helpers', () => {
Expand Down Expand Up @@ -88,14 +88,14 @@ describe('Tests Service Helpers', () => {

it('Test ServiceManager Controller', async () => {
let started = false
const controller = ServiceManager.controller({
initialize: async () => {
const controller = ProcessManager({
start: async () => {
started = true
},
shutdown: async () => {
stop: async () => {
started = false
}
})
}).controller()
await controller.start()
test.value(started).is(true)
await controller.stop()
Expand Down