
Implements the Chain of Responsibility design pattern to streamline business logic processes, enhancing readability and agility, while offering a code-first approach for developer-friendly customization.
This is a Chain of Responsibility (CoR) Design Pattern Library. Its goal is to make business logics as simple as ...:
val bizLogics = rootChain<BizContext> {
initialize("Initialization of the chain")
chooseRepo("Choose test of prod repository")
validation("Validation of the request") {
validateIdNotEmpty("Validate the id to be not empty")
validateIdFormat("Validate the id to have proper format")
finishValidateion("Prepare response on errors")
}
readObject("Reading requested object")
access("Check access rights") {
accessRelations("Compute relationships of the requester to the object")
accessCorPermissions("Compute permissions of the user to the object")
accessValidate("Check the requested operation is permitted")
accessFrontedPeremissions("Compute the user permissions to deliver to frontend")
}
response("Prepare response")
}.build()
val ctx = BizContext(
idRequested = "<Object id from request obtained in controller>"
)
bizLogics.exec(ctx)
assertEquals(expected, ctx.objResponse)Other option includes also custom settings of the pipeline that can be used during initialization and processing:
val settings = BizSettings(
repo = PostgresRepo(),
loggerProvider = { clazz: String -> LoggerFactory.getLogger(clazz) },
)
val bizLogics = rootChain<BizContext, CorSettings>(settings) {
readObject("Object reading from DB")
}
fun ICorDslAdd<BizContext, BizSettings>.readObject(title: String) = worker {
this.title = title
this.description = """
This worker handles reading object in question from DB.
The mandatory parameter is ID
"""
// Getting content of BizSettings to this.config,
// so that logger initialized during configuration step
val logger = this.config.loggerProvider("readObject")
on { state == RUNNING }
handle {
// But we can use logger in runtime area
logger.info("Handling $title")
objectRead = config.repo.read(this.toReadRequest())
}
except { e: Throwable ->
fail(e.toError())
logger.error("Error reading object from DB", e)
}
}Such a representation of the business logics has the following advantages.
BPMS engines provide a "declaration first" approach where business logics is developed in a visual designer. This may be very convenient for analysts, architects or managers but brings few disadvantages to developers. The main problem is current engines use a schema: Visual Editor -> xml spec -> code.
This CoR library doesn't compete with BPM as is. But it allows developers to control the code themselves.
Compatibility between BPM code generators and CoR is planned.
[libraries]
cor = "com.crowdproj:kotlin-cor:0.6.0"repositories {
mavenCentral()
}
dependencies {
implementation(libs.cor)
}First, build a business chain
val chain = rootChain<TestContext> {
// This is a simple worker
worker {
title = "Status initialization"
description = "Check the status initialization at the buziness chain start"
on { status == CorStatuses.NONE }
handle { status = CorStatuses.RUNNING }
except { status = CorStatuses.FAILING }
}
// Chain wraps a series of workers
chain {
on { status == CorStatuses.RUNNING }
worker(
title = "Lambda worker",
description = "Example of a buziness chain worker in a lambda form"
) {
some += 4
}
}
// Nearly the same as `chain` but workers executed in parallel, so the order between them is not guaranteed
parallel {
on {
some < 15
}
worker(title = "Increment some") {
some++
}
}
// You can represent your workers and chains as Kotlin extensions
// In this form they look more compact and easier
printResult()
}.build()Then start it with you context:
val ctx = TestContext(some = 13)
runBlocking { chain.exec(ctx) }This is a Chain of Responsibility (CoR) Design Pattern Library. Its goal is to make business logics as simple as ...:
val bizLogics = rootChain<BizContext> {
initialize("Initialization of the chain")
chooseRepo("Choose test of prod repository")
validation("Validation of the request") {
validateIdNotEmpty("Validate the id to be not empty")
validateIdFormat("Validate the id to have proper format")
finishValidateion("Prepare response on errors")
}
readObject("Reading requested object")
access("Check access rights") {
accessRelations("Compute relationships of the requester to the object")
accessCorPermissions("Compute permissions of the user to the object")
accessValidate("Check the requested operation is permitted")
accessFrontedPeremissions("Compute the user permissions to deliver to frontend")
}
response("Prepare response")
}.build()
val ctx = BizContext(
idRequested = "<Object id from request obtained in controller>"
)
bizLogics.exec(ctx)
assertEquals(expected, ctx.objResponse)Other option includes also custom settings of the pipeline that can be used during initialization and processing:
val settings = BizSettings(
repo = PostgresRepo(),
loggerProvider = { clazz: String -> LoggerFactory.getLogger(clazz) },
)
val bizLogics = rootChain<BizContext, CorSettings>(settings) {
readObject("Object reading from DB")
}
fun ICorDslAdd<BizContext, BizSettings>.readObject(title: String) = worker {
this.title = title
this.description = """
This worker handles reading object in question from DB.
The mandatory parameter is ID
"""
// Getting content of BizSettings to this.config,
// so that logger initialized during configuration step
val logger = this.config.loggerProvider("readObject")
on { state == RUNNING }
handle {
// But we can use logger in runtime area
logger.info("Handling $title")
objectRead = config.repo.read(this.toReadRequest())
}
except { e: Throwable ->
fail(e.toError())
logger.error("Error reading object from DB", e)
}
}Such a representation of the business logics has the following advantages.
BPMS engines provide a "declaration first" approach where business logics is developed in a visual designer. This may be very convenient for analysts, architects or managers but brings few disadvantages to developers. The main problem is current engines use a schema: Visual Editor -> xml spec -> code.
This CoR library doesn't compete with BPM as is. But it allows developers to control the code themselves.
Compatibility between BPM code generators and CoR is planned.
[libraries]
cor = "com.crowdproj:kotlin-cor:0.6.0"repositories {
mavenCentral()
}
dependencies {
implementation(libs.cor)
}First, build a business chain
val chain = rootChain<TestContext> {
// This is a simple worker
worker {
title = "Status initialization"
description = "Check the status initialization at the buziness chain start"
on { status == CorStatuses.NONE }
handle { status = CorStatuses.RUNNING }
except { status = CorStatuses.FAILING }
}
// Chain wraps a series of workers
chain {
on { status == CorStatuses.RUNNING }
worker(
title = "Lambda worker",
description = "Example of a buziness chain worker in a lambda form"
) {
some += 4
}
}
// Nearly the same as `chain` but workers executed in parallel, so the order between them is not guaranteed
parallel {
on {
some < 15
}
worker(title = "Increment some") {
some++
}
}
// You can represent your workers and chains as Kotlin extensions
// In this form they look more compact and easier
printResult()
}.build()Then start it with you context:
val ctx = TestContext(some = 13)
runBlocking { chain.exec(ctx) }