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

Swagger - BRAVE

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