Skip to content

Commit

Permalink
Create separate ProcessController interface (#340)
Browse files Browse the repository at this point in the history
Co-authored-by: Mark Maxham <max@ellis-and-associates.com>
  • Loading branch information
Michael Durling and Mark Maxham authored May 20, 2020
1 parent b7e00f1 commit 8e6f1ba
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 48 deletions.
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

0 comments on commit 8e6f1ba

Please sign in to comment.