Руководство прикладного разработчика#

В настоящем документе приведены инструкции для прикладного разработчика компонента JDBC Gateway (код JDBC), далее — Шлюз БД, из состава программного продукта Platform V Synapse Enterprise Integration (код SEI).

Термины и определения#

Термин/Аббревиатура

Определение

Деплоймент

Файл, содержащий набор инструкций для запуска приложения в Kubernetes

Pod

Абстрактный объект, набор контейнеров в архитектуре Kubernetes

БД

База данных

Курсор

тип параметра в базе данных, результирующий набор строк

gRPС

Высокопроизводительный фреймворк, разработанный компанией Google для вызова удаленных процедур (RPC)

SVPX

Компонент Сервисный прокси Продукт Platform V Synapse Service Mesh (SSM). Применение сетевых политик кластера для внутренних взаимодействий

LOGA

Компонент Журналирование Продукт Platform V Monitor (OPM). Извлечение записей журнала Шлюза БД и передача их в подсистему журналирования

PSQL

Компонент Platform V Pangolin Продукт Platform V Pangolin SE (PSQ). Система управления базами данных, основанная на PostgreSQL

HashiCorp Vault

Компонент HashiCorp Vault, реализующий безопасное хранилище секретной информации

Системные требования#

Системные требования описаны в Руководстве по установке в разделе "Системные требования".

Подключение и конфигурирование#

Подключение Шлюза БД#

  1. Для установки экземпляра Шлюза БД на стенд необходимо в соответствующий проект загрузить конфигурационные артефакты:

Артефакт

Содержание

Описание

Deployment

Параметры запуска контейнера приложения в Kubernetes

Наименование экземпляра приложения, ссылка на образ контейнера приложения, запрашиваемые ресурсы, публикуемые порты, параметры liveness и readiness проб, необходимость и параметры подключения sidecar контейнеров, точки монтирования конфигурационных артефактов в файловую систему контейнера.

ConfigMap

application.yml

Файл содержащий параметры конфигурации приложения

ConfigMap

db_explorer.yml

Файл содержащий параметры вызова процедур/функций в базе данных

ConfigMap

secman.txt

Файл со списком секретов, которые нужно ждать из HashiCorp Vault

Service

Артефакт для регистрации приложения в service discovery Kubernetes

Селекторы и порты для подключения приложения к механизмам распределения трафика Kubernetes

ServiceEntry

Артефакт для регистрации внешнего сервиса в Kubernetes

Содержит адреса хостов и номера портов для подключения к базе данных. Если для нужных хостов и портов уже установлены ServiceEntry в проекте, то грузить их повторно не требуется

  1. Последовательность загрузки артефактов:

    • ConfigMap c application.yml

    • ConfigMap c db_explorer.yml

    • ServiceEntry

    • Deployment

    • Service

  2. Порядок действий при загрузке артефактов в проект описаны в Руководстве по установке раздел Установка Шлюза БД .

Конфигурирование Шлюза БД#

Описание конфигурации application.yml - должна быть определена как ConfigMap

     server:
       port: 8787                                    # порт приложения
     grpc:
       maxMessageSizeInMb: 8                         # максимальный размер сообщения в мегабайтах
       server:
         server-port: 5454                           # порт gRPC сервера 
         threadpool:
           maxconcurrentconsumers: 20                # максимальное количество потоков обработки gRPC-запросов, которое может использовать сервер
           concurrentconsumers: 15                   # количество потоков обработки gRPC-запросов при старте сервера  
           threadkeepaliveseconds: 30                # параметр, который определяет сколько времени (в секундах) неиспользуемые потоки будут оставаться в памяти  
       client:
         settings:
           default:
             hostname: <hostname>                    # имя клиента gRPC
             port: 5454                              # порт клиента gRPC
     service:
       settings:
         enable-param-logging: false                 # логический параметр, отвечающий за журналирование IN/OUT-параметров, передаваемых и получаемых из хранимых процедур
     scheduler:
       enabled: true                                 # логический параметр, отвечающий за отправку метрик жизненного цикла сертификата
       initial-delay: 1000                           # начальная задержка перед отправкой метрик жизненного цикла сертификата в миллисекундах
       check-interval: 10000                         # периодичность отправки метрик жизненного цикла сертификата в миллисекундах
     datasource:
       jdbc-url: <hostname БД>:<port>/<database>     # подключения к базе данных
       maximum-pool-size: 2                          # максимальное количество подключений в пуле базы данных
       pool-name: HHHGGGFFFF                         # имя пула базы данных
       username: <username>                          # логин пользователя базы данных
     ds:
       prop:
 	  	tlsprotocol: TLSv1.2                        # требуемая версия TLS протокола JVM
 	  	cipherSuites: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 # требуемый алгоритм шифрования JVM
         ssl: true                                   # логический параметр, отвечающий за включение ssl соединения
         sslmode: verify-full                        # метод аутентификации при заданном параметре ssl: true
         sslhostnameverifier: com.sbt.synapse.dnverify.DnHostnameVerifier # проверка всего DN серверного сертиката (работает при sslmode=verify-full)
 		sslalloweddn: 'CN=10.xx.xx.xx, O=SBRF, C=RU' # разрешенный список DN серверных сертификатов в формате строки
         dnmaxcount: 1                               # параметр, указывающий максимальное количество разрешенных DN
         sslrootcert: /deployments/config/db/ca.crt  # путь к корневому сертификату
         sslcert: /deployments/config/db/cert.crt    # путь к клиентскому сертификату
         sslkey: /deployments/config/db/key.pk8      # путь к файлу клиентского ключа
     crypto:
       enabled: true                                 # логический параметр, отвечающий за включение/выключение поддержки шифрования
       keyExpirationHours: 24                        # интервал обновления симметричного ключа (в часах)
       keySize: 256                                  # размер симметричного ключа в битах
       asymmetricAlgorithmName: RSA                  # алгоритм ассиметричного шифрования
       asymmetricMode: ECB                           # режим ассиметричного шифрования
       asymmetricPadding: PKCS1Padding               # схема заполнения для ассиметричного шифрования
       symmetricAlgorithmName: AES                   # алгоритм симметричного шифрования
       symmetricMode: GCM                            # режим симметричного шифрования
       symmetricPadding: PKCS7Padding                # схема заполнения для ассиметричного шифрования
       provider: BC                                  # крипто провайдер
       returnAsIsOnError: true                       # если true и при расшифровке сообщения возникнет ошибка Шлюз БД вернет сообщение в исходном виде
                                                     # если он равен false, то при возникновении ошибки выбросит exception и обработка сообщения прекратится
                                                     # флаг нужен для одновременной работы с зашифрованными и незашифрованными данными
     secman:
       role: "role-ga-secman-syte"                   # роль HashiCorp Vault в кластере
       vault-addr: "t.secrets.delta.sbrf.ru"         # стенд HashiCorp Vault
       os-addr: "dev-gen2.delta.sbrf.ru"             # стенд кластера
       vault-path: "CI01994970_CI02129749/A/SNPS/OSH/IFT/KV/CRYPTOCIPHER"   # путь в HashiCorp Vault, где лежат сертификаты шифратора (Важно! Ключи в секрете должны быть названы pem и key)
       vault-ns: "CI01994970_CI02129749"             # имя хранилища в HashiCorp Vault

Описание конфигурации db_explorer.yml - должна быть определена как ConfigMap

 entity:
   entityName: "test"                           # название справочника
   schema: "SRD_LOG"                            # название схемы, в которой хранятся процедуры/функции
   actions:
       - name: "getOrderId"                     # название для связи процедуры с запросом. В запросе записывается в поле `Entity.Action`            
         packageName:                           # название пакета, в котором хранится процедура. Может быть пустым
         functionName: "getorderbyid"           # название функции в базе данных
         commit: true                           # логический параметр, обозначающий фиксацию транзакции (необязательный параметр, требуется для функций с курсорами)
         fields:
           - type: "INTEGER"                    # тип параметра. Поддерживаются типы: `STRING`, `NUMBER`, `DATA`, `TIMESTAMP`, `CLOB`, `BLOB`, `CURSOR`, `INTEGER`, `DOUBLE`, `TEXT`, `BYTEA`, `BIGINT`, `DECIMAL`, `BOOLEAN`.
             name: "orderId"                    # название параметра для связи параметра с запросом.
             nameInProcedure: "order_id"        # название параметра процедуры/функции в базе данных.
             input: true                        # параметр логического типа, обозначающий входящие и исходящие параметры процедур/функций.
             encrypt: false                     # при значении true, входящий параметр отправится в базу данных в зашифрованном виде, а исходящий параметр расшифруется из базы данных
           - type: "CURSOR"
             name: "out_result"
             nameInProcedure: "OUT_CURSOR"
             input: false
             encrypt: true                      # для параметров типа CURSOR, нужно указывать значение шифратора
             cursorColumns:                     # для параметра типа `CURSOR` задается описание каждого столбца курсора.
               - type: "INTEGER"                # тип поля курсора. Поддерживаются типы: `STRING`, `NUMBER`, `DATA`, `TIMESTAMP`, `CLOB`, `BLOB`, `INTEGER`, `DOUBLE`, `TEXT`, `BYTEA`, `BIGINT`, `DECIMAL`, `BOOLEAN`.
                 name: "id"                     # название столбца курсора. Используется для связи с описанием при обращении к полям в Proto-сообщении.
                 nameInProcedure: "id"          # название столбца курсора в процедуре/функции базы данных.
               - type: "INTEGER"
                 name: "customerid"
                 nameInProcedure: "customerid"
               - type: "TIMESTAMP"
                 name: "orderdate"
                 nameInProcedure: "orderdate"
               - type: "NUMBER"
                 name: "totalprice"
                 nameInProcedure: "totalprice"
               - type: "TEXT"
                 name: "description"
                 nameInProcedure: "description"
                 encrypt: true
       - name: "get"
         packageName:
         procedureName: "GETAGGREGATION"      # название процедуры в базе данных
         fields:
           - type: "STRING"
             name: "msgId"
             nameInProcedure: "IN_MSG_ID"
             input: true
           - type: "STRING"
             name: "aggId"
             nameInProcedure: "IN_AGGR_ID"
             input: true
           - type: "CLOB"
             name: "message"
             nameInProcedure: "OUT_MSG_BODY"
             input: false

Описание конфигурации ServiceEntry

     apiVersion: networking.istio.io/v1alpha3
     kind: ServiceEntry
     metadata:
         name: db-service-entry
         labels:
           app: db-gateway
     spec:
         exportTo: 
             -.
         hosts:
           - <hostname БД>             # хост для подключения к базе данных
         ports:
           - name: tcp
             number: 1523              # порт для подключения к базе данных
             protocol: tcp
         resolution: DNS

Описание конфигурации Deployment

 kind: Deployment
 apiVersion: apps/v1
 metadata:
   name: service-db-module
   labels:
     app: service-db-module
     secman-injector: enabled                                                          
 spec:
   replicas: 2
   selector:
     matchLabels:
       app: service-db-module
   strategy:
 	type: RollingUpdate
 	rollingUpdate:
       maxUnavailable: 0
 	  maxSurge: 25%
   template:
     metadata:
       labels:
         app: service-db-module
         name: service-db-module
         secman-injector: enabled                                                              # включение сайдкара vault-agenta ФП HashiCorp Vault         
       annotations:
         sidecar.istio.io/inject: 'true'                                                       # включение/отключение контейнера Компонента «Сервисный прокси» (например, true/false)          
         sidecar.istio.io/proxyCPU: 500m                                                       # ресурсы для cpu request для контейнера Компонента «Сервисный прокси» (например, 500m)
         sidecar.istio.io/proxyCPULimit: 500m                                                  # ресурсы для cpu limit для контейнера Компонента «Сервисный прокси» (например, 500m)
         sidecar.istio.io/proxyMemory: 512Mi                                                   # ресурсы для memory request для контейнера Компонента «Сервисный прокси» (например, 512m)
         sidecar.istio.io/proxyMemoryLimit: 512Mi                                              # ресурсы для memory limit для контейнера Компонента «Сервисный прокси» (например, 512m)

         vault.hashicorp.com/agent-inject: 'true'                                              # включение/отключение sidecar HashiCorp Vault
 		vault.hashicorp.com/namespace: <NAMESPACE_VAULT>                                      # имя созданного хранилища в HashiCorp Vault (например, CI01994970_CI02129749)
 		vault.hashicorp.com/role: <SECMAN_ROLE>                                               # имя полученной роли в HashiCorp Vault (например, ci01994970_ci02129749_a_snps_osh_ift_ac)
 		vault.hashicorp.com/secret-volume-path: <SECMAN_MOUNT_PATH>                           # путь для загрузки секретов в файловой системе Pod (например, /deployments/config/db/certs)
 		vault.hashicorp.com/agent-inject-secret-cert.crt: 'true'                              # включить чтение секрета cert.crt
         vault.hashicorp.com/agent-inject-template-cert.crt: |                                 # создается файл cert.crt в файловой системе
           {{- with secret <SECRET_PATH> -}}                                                   # путь до секрета в HashiCorp Vault (например, CI01994970_CI02129749/A/SNPS/OSH/IFT/KV/JDBC)
           {{ base64Decode .Data.cert }}                                                       # значение из HashiCorp Vault по ключу cert, переведенное из base64
           {{- end }}                                                   
         vault.hashicorp.com/agent-inject-secret-ca.crt: 'true'                                # включить чтение секрета ca.crt
         vault.hashicorp.com/agent-inject-template-ca.crt: |                                   # создается файл ca.crt
           {{- with secret <SECRET_PATH> -}}                                                   # путь до секрета в HashiCorp Vault (например, CI01994970_CI02129749/A/SNPS/OSH/IFT/KV/JDBC)
           {{ base64Decode .Data.ca }}                                                         # значение из HashiCorp Vault по ключу ca, переведенное из base64
           {{- end }}
         vault.hashicorp.com/agent-inject-secret-key.pk8: 'true'                               # включить чтение секрета key.pk8
         vault.hashicorp.com/agent-inject-template-key.pk8: |                                  # создается файл key.pk8 в файловой системе
           {{- with secret <SECRET_PATH> -}}                                                   # путь до секрета в HashiCorp Vault (например, CI01994970_CI02129749/A/SNPS/OSH/IFT/KV/JDBC)
           {{ base64Decode .Data.key }}                                                        # значение из HashiCorp Vault по ключу key, переведенное из base64
           {{- end }}
         vault.hashicorp.com/agent-inject-secret-db-secret.yml: 'true'                         # включить чтение секрета db-secret.yml
         vault.hashicorp.com/agent-inject-template-db-secret.yml: |                            # создается файл db-secret.yml
           {{- with secret <SECRET_PATH> -}}                                                   # путь до секрета (например, CI01994970_CI02129749/A/SNPS/OSH/IFT/KV/JDBC)
           {{ base64Decode .Data.secret }}                                                     # значение из HashiCorp Vault по ключу db-secret.yml, переведенное из base64
           {{- end }}
 	  	prometheus.io/path: /actuator/prometheus											  # путь контроллера, с которого собирать метрики
 		prometheus.io/scheme: http															  # схема работы, по которой будет отправлять метрики prometheus
 		prometheus.io/port: '8787'															  # порт, на котором доступен web server для prometheus
     spec:
       volumes:
         - name: application-config
           configMap:
             name: service-db-module-app-config                                               # ConfigMap c параметрами конфигурации Шлюза БД
             items:
               - key: application.yml
                 path: application.yml
             defaultMode: 256
         - name: entity-config
           configMap:
             name: service-db-module-entity-config                                           # ConfigMap c параметрами вызова процедур/функций в базе данных
             items:
               - key: db_explorer.yml
                 path: db_explorer.yml
             defaultMode: 256
         - name: synapselogs
           emptyDir: {}
         - name: db-postgre-secman
           configMap:
             name: db-postgre-secman                                                         # ConfigMap со списком секретов, которые нужно ждать из HashiCorp Vault
             items:
               - key: secman.txt
                 path: secman.txt
             defaultMode: 256
       containers:
         - resources:
             limits:
               cpu: 500m                                                                   # cpu limit для контейнера приложения (например, 500m)
               memory: 700Mi                                                               # memory limit для контейнера приложения (например, 700Mi)
             requests:
               cpu: 500m                                                                   # cpu request для контейнера приложения (например, 500m)
               memory: 700Mi                                                               # memory request для контейнера приложения (например, 700Mi)
           readinessProbe:
             httpGet:
               path: /actuator/health/readiness
               port: 8787
               scheme: HTTP
             initialDelaySeconds: 60
             timeoutSeconds: 5
             periodSeconds: 10
             successThreshold: 1
             failureThreshold: 3
           terminationMessagePath: /dev/termination-log
           securityContext:
 		  	capabilities:
 			  drop:
 				- ALL
 		  	privileged: false
             runAsNonRoot: true
             readOnlyRootFilesystem: true
           name: service-db-module
           livenessProbe:
             httpGet:
               path: /actuator/health/liveness
               port: 8787
               scheme: HTTP
             initialDelaySeconds: 60
             timeoutSeconds: 5
             periodSeconds: 10
             successThreshold: 1
             failureThreshold: 5
           env:
             - name: TZ
               value: Europe/Moscow
             - name: PROJECT_NAME
               valueFrom:
                 fieldRef:
                   apiVersion: v1
                   fieldPath: metadata.namespace
             - name: JAVA_OPTS
               value: '-XX:+UseG1GC -XX:+ExitOnOutOfMemoryError'                             # параметры запуска JVM (например, -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError)
           ports:
             - containerPort: 5454
               protocol: TCP
             - containerPort: 8787
               protocol: TCP
           imagePullPolicy: Always
           volumeMounts:
             - name: application-config
               readOnly: true
               mountPath: /deployments/config
             - name: entity-config
               readOnly: true
               mountPath: /deployments/config/entity
             - name: synapselogs
               mountPath: /opt/synapse/logs
             - name: db-postgre-secman
               readOnly: true
               mountPath: /tmp/secman/config
           terminationMessagePolicy: File
           image: >-
             <ссылка на Docker-образ Шлюза БД в целевом репозитории>                       # ссылка на Docker-образ Шлюза БД
       restartPolicy: Always
       terminationGracePeriodSeconds: 60
       dnsPolicy: ClusterFirst

Описание конфигурации Service

 apiVersion: v1
 kind: Service
 metadata:  
   name: service-db-module
   labels: { app: service-db-module }
 spec:
   selector: { app: service-db-module}                                                   # Селектор для подключения приложения Шлюза БД 
   ports:																				# Порт для подключения приложения Шлюза БД                                         
 	- name: grpc
 	  port: 5454
 	  targetPort: 5454
 	- name: http
 	  protocol: TCP
 	  port: 8787
 	  targetPort: 8787

Миграция на текущую версию#

Для миграции на текущую версию требуется:

  1. Уточнить в разделе Примечания к релизу необходимость внесения изменений в конфигурацию Шлюза БД при переходе на текущую версию.

  2. При необходимости подготовить ConfigMap с новой конфигурацией.

  3. Подготовить артефакт Deployment, в котором заменить ссылку на Docker-образ Шлюза БД в репозитории ссылкой на Docker-образ с текущей версией.

  4. Если это указано в разделе Примечания к релизу, изменить значения выделяемых ресурсов.

  5. Остановить Шлюз БД.

  6. Загрузить новую конфигурацию Шлюза БД.

  7. Загрузить новый Deployment.

  8. Запустить Шлюз БД (если в Deployment указано количество реплик > 0, Шлюз БД запустится автоматически).

Дополнительно:

Порядок действий при обновлении - см. в Руководстве по установке раздел Обновление.
Остановка/запуск Шлюза БД - см. в разделе Руководство по системному администрированию

Быстрый старт#

Установка компонентов#

Порядок действий для подключения Шлюза БД, общих компонентов, сервера базы данных описан в Руководстве по установке раздел Установка .

Настройка базы данных#

Для настройки базы данных требуется создать в базе данных необходимые PL/SQL-процедуры, предварительно создав пользователя базы данных и предоставив ему права на вызов процедур. Инструкция по созданию пользователя и выдаче прав в базе данных находится в Руководстве по системному администрированию раздел Авторизация.

Запуск и проверка работоспособности Шлюза БД#

Запустите Pod Шлюз БД в Kubernetes (подробнее в Руководстве по системному администрированию раздел Изменение количества Pod).
Проверка работоспособности Шлюза БД описана в Руководстве по установке разделе Проверка работоспособности.
Проверки корректности работы Шлюза БД описаны в Программе и методике испытаний разделе Методы испытаний.

Использование программного компонента#

Использование пользовательских метрик мониторинга#

Формирование метрик мониторинга осуществялется с помощью Spring Actuator. Метрики в формате prometheus доступны по адресу:
< ip >:< spring_boot_server_port >/actuator/prometheus.
Помимо стандартных метрик Spring Boot приложения добавлена метрика "sended_requests_total", показывающая общее количество вызовов каждой процедуры и функции.

Вызов Шлюза БД по gRPC из другого приложения#

Для вызова Шлюза БД необходимо подключить к приложению, делающему вызов, библиотеки com.sbt.synapse:synapse-esb-database-api и com.sbt.synapse:synapse-grpc-core:

dependencies {
 implementation "com.sbt.synapse:synapse-esb-database-api:1.3.0"
 implementation "com.sbt.synapse:synapse-grpc-core:2.0.34"
}

Также необходимо задать адрес подключения к Шлюзу БД в spring-настройках. Обычно это делается в файле application.yml для самостоятельных spring-приложений или в ConfigMap для Kubernetes-сервисов:

grpc:
  client:
    settings:
      channelName:
      hostname: <grpc server host>                # имя хоста grpc сервера Шлюза БД, указанное в Service Шлюза БД   
      port: <grpc server port>                    # порт grpc сервера Шлюза БД, указанный в Service Шлюза БД

Вызов осуществляется следующим образом:

 @Service
 public class Scenario {
     // Ссылка на Шлюз БД
     @Grpc(serviceName = "com.sbt.synapse.database.DbService/dbCallStoredProcedure")
     GrpcService<ProtoDBAction, ProtoDBAction> grpcService;
     public void callDBcreateOrder(Order order){
     // Вызов Шлюза БД
     ProtoDBAction.newBuilder().setMessageId("1111-2222-3333").setCorrelationId("9999-8888-7777").setBody("big big body")
                        .setEntity(Entity.newBuilder().setName("name").setAction("createOrder")
                        .putFields("customerid", BuilderUtils.singleParam(String.valueOf(order.getCustomerId())))
                        .putFields("totalprice", BuilderUtils.singleParam(String.valueOf(order.getTotalPrice())))
                        .putFields("description", BuilderUtils.singleParam(String.valueOf(order.getDescription()))).putAllSystemHeaders( new HashMap<String, String>() {{
                             put("x-synapse-rquid", "rquid1");
                             put("x-synapse-operationname", "operation2");
                        }}).putAllUserHeaders(new HashMap<String, String>() {{
                             put("RqUID", "rquid1");
                             put("OperationName", "operation2");
                             put("ServiceName", "initService");
                        }})).build();
             CallInfo<ProtoDBAction, ProtoDBAction> call = grpcService.invoke(protoDBAction);
             try {
                 ProtoDBAction response = call.execute();
                 if (response.hasEntity()) { // Обработка прошла успешно
                     String newIdStr = BuilderUtils.getParamValue(response, "orderid");
                 } else if (response.hasProcedureAppException()) {
                     log.error("Процедура вернула ошибку через RAISE_APPLICATION_ERROR");
                 }
             } catch (Exception e) {
                 log.error("ERROR with grpc invoking", e);
             }
     }
 }

Также поддерживаются асинхронные вызовы:

 @Service
 public class Scenario {
 // Ссылка на Шлюз БД
 @Grpc(serviceName = "com.sbt.synapse.database.DbService/dbCallStoredProcedure")
 GrpcService<ProtoDBAction, ProtoDBAction> grpcService;
 public void testSynapseStubAsync(ProtoDBAction request) throws Exception {
         CountDownLatch latch = new CountDownLatch(10);
         CallInfo<ProtoDBAction, ProtoDBAction> call = grpcService.invoke(request).channel("agg");
         long start = System.currentTimeMillis();
         for (int i = 0; i < 10; i++) {
             call.executeAsync(new FutureCallback<ProtoDBAction>() {
                 @Override
                 public void onSuccess(@NullableDecl ProtoDBAction result) {
                     log.info("Time to result: " + (System.currentTimeMillis() - start));
                     assertTrue(result.getEntity().getFieldsMap().isEmpty());
                     latch.countDown();
                 }
 
                 @Override
                 public void onFailure(Throwable t) {
                     t.printStackTrace();
                     fail("Request failed");
                 }
             });
         }
         long end = System.currentTimeMillis();
         log.info("Time to request: " + (end - start));
         latch.await(10L, TimeUnit.SECONDS);
 }
}

Пусть существует процедура получения времени создания документа по его идентификатору:

         create function createordercipherreturnid(customer_id integer, total_price numeric, descr text) returns integer
             language plpgsql
         as
         $$
         DECLARE
             order_id integer;
         BEGIN
             INSERT INTO synapse."order"(customerid,orderdate,totalprice,description)
             VALUES  (customer_id, CURRENT_TIMESTAMP,total_price,descr) RETURNING id into order_id;
             RETURN order_id;
         END;
         $$;

Тогда справочник db_explorer.yaml в ConfigMap — service-db-module-entity-config может иметь следующий вид:

    entity:
      entityName: "test"
      schema: synapse
      actions:
          - name: "createOrder"
            packageName:
            functionName: "createordercipherreturnid"
            commit: true
            fields:
              - type: "INTEGER"
                name: "customerid"
                nameInProcedure: "customer_id"
                input: true
              - type: "NUMBER"
                name: "totalprice"
                nameInProcedure: "total_price"
                input: true
              - type: "TEXT"
                name: "description"
                nameInProcedure: "descr"
                input: true
                encrypt: true
              - type: "INTEGER"
                name: "orderid"
                nameInProcedure: "order_id"
                input: false

Если рассмотреть пример с обращением к функции createordercipherreturnid из примера выше, то в соответствии со справочником объект request необходимо будет сформировать следующим образом:

 ProtoDBAction.newBuilder().setEntity(Entity.newBuilder().setName("name").setAction("createOrder")
                            .putFields("customerid", BuilderUtils.singleParam(String.valueOf(order.getCustomerId())))
                            .putFields("totalprice", BuilderUtils.singleParam(String.valueOf(order.getTotalPrice())))
                            .putFields("description", BuilderUtils.singleParam(String.valueOf(order.getDescription()))).putAllSystemHeaders( new HashMap<String, String>() {{
                                 put("x-synapse-rquid", "rquid1");
                                 put("x-synapse-operationname", "operation2");
                            }}).putAllUserHeaders(new HashMap<String, String>() {{
                                 put("RqUID", "rquid1");
                                 put("OperationName", "operation2");
                                 put("ServiceName", "initService");
                            }})).build();

Сигнатуры доступных методов#

dbCallStoredProcedure (ProtoDBAction) returns (ProtoDBAction) — обращение к хранимой процедуре базы данных.

Необходимые данные из ответа можно получить следующим образом:

 ProtoDBAction response = call.execute();
                     if (response.hasEntity()) { 
                         String newIdStr = BuilderUtils.getParamValue(response, "orderid");
                     }

Если проекте не используется библиотека synapse-grpc-core, то вызов можно осуществить, используя базовую gRPC-библиотеку io.grpc.grpc-all. Вызов осуществляется следующим образом:

 ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
 try {
  ProtoDBAction response = DbServiceGrpc.newBlockingStub(channel).dbCallStoredProcedure(request);
      if (response.hasEntity()) {
      // Обработка прошла успешно
      } else if (response.hasProcedureAppException()) {
      // Процедура вернула ошибку через RAISE_APPLICATION_ERROR
      }
 } catch (Exception e) {
  // Вызов закончился с ошибкой
 }
 channel.shutdown();

Для асинхронных вызовов:

     ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
     ListenableFuture<ProtoDBAction> future = DbServiceGrpc.newFutureStub(channel).dbCallStoredProcedure(request);
     Futures.addCallback(future, new FutureCallback<ProtoDBAction>() {
          @Override
          public void onSuccess(@NullableDecl ProtoDBAction response) {
              if (response != null) {
                  if (response.hasEntity()) {
                  // Обработка прошла успешно
                  } else if (response.hasProcedureAppException()) {
                  // Процедура вернула ошибку через RAISE_APPLICATION_ERROR
                  }
              }
          }
         
          @Override
          public void onFailure(Throwable t) {
          // Вызов закончился ошибкой
          }
     }, executor);

Логирование#

В процессе работы Шлюз БД фиксирует события в логах.

Системное логирование#

В системном логе фиксируются события, возникающие в приложении в процессе его работы. По умолчанию системный лог выводится в консоль контейнера приложения.

Доступ к системному логу можно получить через Web-интерфейс Kubernetes (Workloads->Pods->Имя pod->вкладка Logs).

Системный лог в формате txt может быть выгружен через консоль клиента Kubernetes командой:

kubectl logs -c <имя контейнера> <имя pod> > <имя файла>.txt

Канал вывода лога, уровень логирования и шаблон записи события задается файле logback.xml. Для изменения настроек по умолчанию в конфигурации шлюза может быть задано иное положение файла logback.xml:

logging:
    config:

Также уровень системного логирования можно изменить, задав его в конфигурации шлюза БД. Уровни ведения журнала на уровне пакетов можно установить через файл конфигурации application.yml (ConfigMap "service-db-module-app-config"):

    logging:
      level:
        com.sbt.synapse: debug              # TRACE, DEBUG, INFO, WARN, ERROR, FATAL или OFF
        com.sbt.synapse.log: trace

Таблица – Уровни логирования

Уровень логирования

Детальное описание

OFF

сообщения не выводятся

FATAL

ошибка, после которой приложение уже не сможет работать и будет остановлено

ERROR

уровень ошибок, когда есть проблемы, которые нужно решить. Ошибка не останавливает работу приложения в целом. Остальные запросы могут работать корректно

WARN

обозначаются логи, которые содержат предостережение. Произошло неожиданное действие, несмотря на это система устояла и выполнила запрос

INFO

лог, который записывает важные действия в приложении. Это не ошибки, это не предостережение, это ожидаемые действия системы

DEBUG

логи, необходимые для отладки приложения. Для уверенности в том, что система делает именно то, что от нее ожидают, или описания действия системы (не рекомендуется для использования в ПРОМ среде)

TRACE

информация для точной отладки (не рекомендуется для использования в ПРОМ среде)

Примеры событий уровней логирования

2022-10-09 20:53:11.261 [DEBUG] [[grpc-transport]-1] [c.s.s.t.g.ServerTracerHeadersInterceptor] [T:] - Метадата входящего вызова: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.39.0,grpc-accept-encoding=gzip,grpc-timeout=60000m,x-forwarded-proto=http,x-request-id=e9743312-4ade-9dae-b098-219b8a10c7bc,x-envoy-expected-rq-timeout-ms=60000,x-b3-traceid=d751cf4d197425ea1f9765cdea09c78f,x-b3-spanid=1f9765cdea09c78f,x-b3-sampled=1)
2022-10-09 20:53:11.263 [INFO ] [[grpc-transport]-1] [com.sbt.synapse.log.ServiceLogger] [T:] - Receive dbRequest entity name: test, action: getOrderId,
2022-10-09 20:53:11.263 [INFO ] [[grpc-transport]-1] [com.sbt.synapse.log.ServiceLogger] [T:] - In Parameter orderId value 76
2022-10-09 20:53:11.263 [DEBUG] [[grpc-transport]-1] [c.s.s.l.g.SynapseMessageLoggerImpl] [T:] - Логирование события Получен запрос: getOrderId к базе данных. RqUID: null
2022-10-09 20:53:11.278 [DEBUG] [[grpc-transport]-1] [c.s.s.l.g.SynapseMessageLoggerImpl] [T:] - Логирование события Запрос: getOrderId к базе данных выполнен успешно. RqUID: null

Прикладное логирование#

В прикладном логе фиксируются следующие шаги прохождения интеграционной цепочки:

  • Шлюз БД получил сообщение по gRPC от микросервиса;

  • Шлюз БД вызвал процедуру/функцию в базе данных;

  • Шлюз БД получил ответ из базы данных;

  • Шлюз БД вызвал микросервис по gRPC и отправил ему сообщение;

  • Ошибка на Шлюзе БД.

Для корректного отображения в прикладном логе в Шлюзе БД должен быть заданы:

  • Заголовок x-synapse-rquid;

  • Заголовок x-synapse-operationname;

  • Заголовок RqUID;

  • Заголовок OperationName;

  • Переменная ServiceName.

Прикладной лог пишется в контейнер приложения. По умолчанию прикладной лог пишется в файлы:

/opt/synapse/logs/messages*.log

Прикладные логи могут быть извлечены из контейнера приложения и отправлены в подсистему журналирования платформы. Для этого в deployment Шлюза БД должен быть сконфигурирован sidecar-контейнер с компонентом платформы Synapse — Агентом журналирования.

Порядок подключения и использования Агента журналирования приведены в разделе Подключение и конфигурирование документа Руководство прикладного разработчика на компонент Сервис Журналирование (LOGA) Platform V Backend Platform.

Часто встречающиеся проблемы и пути их устранения.#

При работе с OUT-параметрами типа CLOB возникают ограничения, связанные с особенностями реализации spring-jdbc. Так, отсутствует возможность получить параметр из процедуры LOB напрямую. Шлюз БД ожидает, что LOB-данные будут входить в состав курсора.

Следовательно, процедура, которой необходимо передать ответ в виде CLOB или BLOB, должна передавать его в виде курсора. Пример реализации такого подхода на уровне PL/SQL-кода приведен ниже:

CREATE OR REPLACE PROCEDURE GET_MESSAGE(IN_MSG_ID IN VARCHAR2, OUT_MSG_BODY OUT SYS_REFCURSOR) AS
BEGIN
 OPEN OUT_MSG_BODY FOR
 SELECT MESSAGE_BODY FROM MESSAGE_TABLE WHERE MSG_ID = IN_MSG_ID;
END GET_MESSAGE;

Если хранимая процедура формирует исключения через RAISE_APPLICATION_ERROR, то исключение обрабатывается и возвращается инициатору запроса в виде ProtoDBAction.ProcedureAppException. На стороне Шлюза БД это будет считаться нормальным поведением и будет отражено в журнале как штатная обработка запроса к базе.