GitHub: https://github.com/SafiBank/SaFiMono/tree/main/services/output-manager
Overview
Sending outbound messages via various channels (push notifications, email, SMS, viber) to particular customer from microservices is available via REST API provided by Output manager. It is a self-service that enables clients to define their own templates/messages.
API
Invocation
There is one POST endpoint per each available message template. Each endpoint consumes object, that contains:
idempotencyId
customerId
channel (optional attribute, PUSH notifications channel is used if not specified; other options are SMS, EMAIL and VIBER)
metadata (optional attribute, used only with PUSH notifications to send additional data into app (primarily deeplink))
additional attributes that are defined as placeholders in the message template to be sent
Push notification displayed on the device contains title - “SaFi bank notification“ (this is default, if title is not explicitly specified within template, see Configuration below) and body - final message with resolved placeholders.
No i18n is in place of now.
Configuration
To enable new type/template of push notification, one needs to add new entry to messageTemplates.json configuration file; which has following format:
{ "id": "17c9cd5a-82d3-4eec-8822-fc9e7a96f33c", "template": "Card was charged with {amount} {currency} at {time}", "templateFriendly" : "[Friendly] Card was charged with {amount} {currency} at {time}", "templateTaglish" : "[Taglish] Card was charged with {amount} {currency} at {time}", "templateTaglishFriendly" : "[TaglishFriendly] Card was charged with {amount} {currency} at {time}", "apiName": "cardCharged", "domain": "CARDS" },
id - randomly generated UUID
template - message template with placeholders enclosed in “{}“, that will be replaced by actual values provided in the request
templateFriendly - message template, in friendly English
templateTaglish - message template, in Taglish
templateTaglishFriendly - message template, in friendly Taglish
apiName - user-friendly endpoint name
title - optional attribute; if specified, it overrides default value for title (“SaFi bank notification“)
These messages are supposed to be reviewed and translated by the Marketing department. Currently, the translations are missing, that’s why we use tags [Friendly]
,[Taglish]
and [TaglishFriendly]
to differentiate between the messages. Once the final translations are in place, there will be no need to use such tags anymore.
After merging updated configuration file to main branch, new version of output-manager microservice is deployed and new endpoints are available.
Meiro Endpoints
Apart from the generated endpoits, output-manager exposes the following endpoints for Meiro integration:
POST /meiro/broadcast -
broadcast a notification/message to multiple customers
Payload:
{ "channel": "PUSH", "title": "string", "text": "string", "metadata": { "additionalProp1": "string", "additionalProp2": "string", "additionalProp3": "string" }, "customerIdList": [ "string" ] }
POST /meiro/send -
send specific messages to customers
Payload:
{ "messages": [ { "customerId": "string", "channel": "PUSH", "title": "string", "text": "string", "metadata": { "additionalProp1": "string", "additionalProp2": "string", "additionalProp3": "string" } } ] }
Internals
API generation is done as part of the included build buildSrc, which occurs before the main project's build and which executes following actions:
as part of the build task, shadow jar is build from the main and buildSrc sources with main class set to ApiGenerator
build task is finalized by generateApi task, which invokes main method of the ApiGenerator - this generates controller and DTO classes based on messageTemplates.json
generateApi task is finalized by ktlintFormat task, which puts the generated code to proper shape
After this included build is done, main build is configured to depend on copyGenerated task which copies generated classes to the location present in the main module's source set (configured in main build.gradle.kts) and they are now part of the application.
How to setup output manager environment
1: gradle build
command will call the method in ph.safibank.outputmanager.util.ApiGenerator.kt
to generate the MessageController and dto classes.
there are two methods in this file.
generateController
: Get all the messages in json file /SaFiMono/services/output-manager/buildSrc/src/main/resources/messageTemplates.json
and it will translate echo item to a method in controller
val apis: List<FunSpec> = templates.map { val apiStatements = createApiStatements(it.apiName) val funSpecBuilder = FunSpec.builder(it.apiName) .addParameter(MESSAGE_PARAMETER_NAME, getMessageClass(it.apiName)) .returns(getMessageClass(it.apiName)) .addAnnotation( AnnotationSpec.builder(Post::class).addMember("%S", "/${it.apiName}") .build() ) apiStatements.forEach { statement -> funSpecBuilder.addStatement(statement) } funSpecBuilder.build() } val controllerFile: FileSpec = FileSpec.builder(PACKAGE_NAME_BASE, CONTROLLER_NAME).addType( TypeSpec.classBuilder(CONTROLLER_NAME) .primaryConstructor( FunSpec.constructorBuilder() .addParameter(SERVICE_PROPERTY_NAME, MessagingService::class) .build() ) ...... .addAnnotation( AnnotationSpec.builder(ExecuteOn::class) .addMember("io.micronaut.scheduling.TaskExecutors.IO") .build() ) ...... .addFunctions(apis) .build() ) // non-standard method import, has to be added explicitly .addImport(GETLOGGER_PACKAGE, GETLOGGER_METHOD) .build() controllerFile.writeTo(Path(TARGET_DIRECTORY))
e.g. it will translate below json information to a method in message controller file.
from:
{ "id": "17c9cd5a-82d3-4eec-8822-fc9e7a96f33c", "template": "Card was charged with {amount} {currency} at {time}", "apiName": "cardCharged" },
to:
@Post("/cardCharged") public fun cardCharged(message: CardChargedMessageDto): CardChargedMessageDto { log.info("Sending message of type: cardCharged") messagingService.sendMessage(message.toMessage()) return message }
generateDtos
: Generate dto entities and output the kotlin file to /SaFiMono/services/output-manager/buildSrc/build/generated/main/kotlin/ph/safibank/outputmanager/controller/model
e.g. translate below json item to an dto class file
from:
{ "id": "17c9cd5a-82d3-4eec-8822-fc9e7a96f33c", "template": "Card was charged with {amount} {currency} at {time}", "apiName": "cardCharged" },
to:
public data class CardChargedMessageDto( public val idempotencyId: UUID, public val customerId: UUID, public val channel: DeliveryChannel?, public val metadata: Map<String, String>?, public val amount: String, public val currency: String, public val time: String, ) { public fun toMessage(): InternalChannelMessageDto { val params = mapOf<CharSequence, CharSequence>( "amount" to amount, "currency" to currency, "time" to time, ) val metadata = mutableMapOf<CharSequence, CharSequence>() this.metadata?.forEach { metadata.put(it.key, it.value) } return InternalChannelMessageDto( idempotencyId, customerId, channel ?: DeliveryChannel.PUSH, """SaFi bank notification""", params, """Card was charged with {amount} {currency} at {time}""", """""", """""", """""", metadata ) } }
2: Go to /SaFiMono/services/output-manager/buildSrc
directory to execute gradle build
command to generate MessageController file and dto classes
buildSrc git:(main) gradle build > Task :kaptKotlin Generating OpenAPI Documentation Writing OpenAPI YAML to destination: /SaFiMono/services/output-manager/buildSrc/build/tmp/kapt3/classes/main/META-INF/swagger/output-manager-0.1.yml Creating bean classes for 5 type elements BUILD SUCCESSFUL in 21s 27 actionable tasks: 17 executed, 10 up-to-date
Attachments:
message-controller.png (image/png)
message-controller.png (image/png)
generator.png (image/png)
截屏2022-09-22 16.53.18.png (image/png)
截屏2022-09-22 16.58.16.png (image/png)
截屏2022-09-22 16.59.16.png (image/png)