Tài liệu phát triển dịch vụ sử dụng APIM và LifeESB


Tổng quan kiến trúc hệ thống


Tổng quan kiến trúc hệ thống

Nội dung chi tiết

1.1. Giới Thiệu

Hệ thống được xây dựng theo mô hình Enterprise Service Bus (ESB) kết hợp API Management (APIM), sử dụng nền tảng LifeESB làm trung gian điều phối giữa Client và các Backend Service. Tích hợp thêm Apache Kafka để xử lý tải lớn theo mô hình bất đồng bộ.

Tên hệ thống: LifeESB APIM: API Manager Message Queue: Apache Kafka (Broker: hdp-master:9092)


1.2. Sơ Đồ Kiến Trúc Tổng Thể

service-architecture-layered.jpg

 


1.3. Các Thành Phần Chính

1.3.1. APIM — API Manager

Cổng vào duy nhất (Single Entry Point) cho toàn bộ hệ thống.

Chức năng Mô tả
Xác thực Kiểm tra JWT / OAuth2 / API Key trước khi vào LifeESB
Rate Limiting Giới hạn số request/giây theo từng API hoặc User
Versioning Quản lý nhiều version API (/v1/v2) song song
Analytics Theo dõi lưu lượng, latency, error rate
Portal Developer Portal để publish và subscribe API

1.3.2. LifeESB

Lớp tích hợp trung gian, chịu trách nhiệm điều phối luồng dữ liệu.

Thành phần File Chức năng
Sync API

PlanningDirectApi.xml
Gọi thẳng Backend, Client nhận kết quả ngay
Async API

KafkaProducerApi.xml
Đẩy vào Kafka, trả phản hồi ngay cho Client
Inbound Endpoint

Load_balance_example.xml
Lắng nghe Kafka, kích hoạt Sequence xử lý
Sequence

Load_balance_example-inboundSequence.xml
Logic xử lý message: gọi Backend + phân loại lỗi
Error Sequence

Load_balance_example-inboundErrorSequence.xml
Xử lý lỗi Kafka, đẩy vào error_topic
Local Entry

KafkaConnection.xml
Cấu hình kết nối Kafka tái sử dụng

1.3.3. Apache Kafka

Message Broker xử lý tải lớn và đảm bảo không mất dữ liệu.

Topic Mục đích
test_topic_01 Nhận message từ API Producer (đầu vào)
processed_topic Lưu message đã xử lý thành công
error_topic Lưu message lỗi để review thủ công

1.3.4. Backend Service

REST API xử lý nghiệp vụ thực tế.

Thông tin Giá trị
Base URL http://192.168.0.133:8080
Endpoint chính POST /api/v1/plannings
Xác thực JWT Bearer Token
Framework Spring Boot

1.4. Hai Mô Hình Dịch Vụ

1.4.1. Đồng Bộ — Synchronous (Direct API)

Client gọi và chờ kết quả từ Backend.



Client → APIM → LifeESB (PlanningDirectApi)
Backend API [chờ response]
LifeESB → Client [trả kết quả thực]

Đặc điểm:

Endpoint: POST /plannings-direct

1.4.2. Bất Đồng Bộ — Asynchronous (Kafka)

Client gửi và nhận phản hồi ngay (không chờ Backend xử lý xong).



ho-so-core-flow (1).jpg

Đặc điểm:

Endpoint: POST /kafka-producer


1.5. So Sánh Hai Mô Hình

Tiêu chí Đồng bộ (/plannings-direct) Bất đồng bộ (/kafka-producer)
Client chờ ✅ Chờ kết quả thực ❌ Nhận success ngay lập tức
Khả năng chịu tải Thấp hơn (Backend block) Cao (Kafka buffer)
Độ trễ Phụ thuộc Backend Gần như bằng 0 (phía Client)
Đảm bảo dữ liệu Ghi ngay hoặc lỗi ngay Kafka lưu, retry nếu fail
Xử lý lỗi faultSequence → trả ngay error_topic → review sau
Phù hợp Tải vừa, cần confirm Tải lớn, xử lý nền
Timeout 15 giây Không (Kafka giữ message)

1.6. Nguyên Tắc Kiến Trúc

  1. Single Entry Point: Mọi request đi qua APIM, không gọi thẳng LifeESB từ ngoài.
  2. Stateless Mediator: LifeESB không lưu trạng thái — toàn bộ state trong Kafka hoặc Backend DB.
  3. Error Isolation: Lỗi phải được bắt trong Sequence, không để lỗi raw thoát ra ngoài.
  4. Dead Letter Pattern: Message lỗi luôn được chuyển vào error_topic, không bao giờ bị drop.
  5. Observability: Mọi bước xử lý đều có log level="custom" để trace theo luồng.

1.7. Môi Trường Triển Khai

Thành phần Host / Port Ghi chú
LifeESB 192.168.0.167:8290 HTTP endpoint
LifeESB Management 192.168.0.167:9201 Deploy CAR file
Kafka Broker hdp-master:9092 PLAINTEXT
Backend API 192.168.0.133:8080 Spring Boot

📸 [Ảnh minh họa] — LifeESB Dashboard sau khi deploy

 

image.png

📸 [Ảnh minh họa] — Kafka UI / Kafka Manager hiển thị các topic

Chương 1: Setup môi trường

Chương này hướng dẫn chuẩn bị môi trường phát triển cho dự án, bao gồm cài đặt công cụ cần thiết, cấu hình biến môi trường, thiết lập IDE và kiểm tra kết nối đến các dịch vụ phụ trợ (DB, cache, message broker…). Sau khi hoàn thành, lập trình viên có thể chạy dự án ở môi trường local.

Chương 1: Setup môi trường

Hướng dẫn setup môi trường và start project

1. Mục tiêu

Hướng dẫn developer:


2. Yêu cầu hệ thống

2.1. Phần mềm cần cài đặt

Công cụ Phiên bản khuyến nghị
Java JDK 17 (bắt buộc)
Maven 3.8+
Git Mới nhất
IDE IntelliJ IDEA 2023+
PostgreSQL 14 hoặc 15
DB Tool DBeaver / PgAdmin

2.2. Kiểm tra cài đặt

Mở terminal:


java -version

Kết quả mong muốn:


java version "17.0.x"

Kiểm tra Maven:


mvn -v

3. Clone source code


4. Cấu hình biến môi trường

Project sử dụng file:

application.properties

4.1. Các biến môi trường cơ bản

Ví dụ file:


application.properties

server: port: 8080 spring: datasource: url: jdbc:postgresql://localhost:5432/lifetex username: lifetex pass

#spring.application.name=demo
#server.port=8080
#
## ===== Oracle datasource =====
#spring.datasource.url=jdbc:oracle:thin:@//192.168.0.111:1111/ORCLPDB1
#spring.datasource.username=test
#spring.datasource.password=test
#spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
#
## ===== JPA =====
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.database-platform=org.hibernate.dialect.OracleDialect
#
## ===== Hikari pool (optional) =====
#spring.datasource.hikari.maximum-pool-size=10
#spring.datasource.hikari.minimum-idle=2
#spring.datasource.hikari.connection-timeout=30000
#
#management.endpoints.web.exposure.include=health,info

4.2. Cách chỉnh sửa biến môi trường

Cách 1: Sửa trực tiếp trong file application.properties

Ví dụ đổi DB:


spring: datasource: url: jdbc:postgresql://localhost:5432/test_db username: test password: test123

Cách 2: Dùng biến môi trường hệ điều hành

Ví dụ trong application.properties:


spring: datasource: url: ${DB_URL} username: ${DB_USER} password: ${DB_PASS}

Trên Windows (PowerShell)


$env:DB_URL="jdbc:postgresql://localhost:5432/lifetex" $env:DB_USER="lifetex" $env:DB_PASS="123456"

Trên Linux/Mac


export DB_URL=jdbc:postgresql://localhost:5432/lifetex export DB_USER=lifetex export DB_PASS=123456

5. Chọn profile chạy

Spring Boot thường dùng profile:


dev staging prod

Ví dụ chạy với profile dev.


Cách cấu hình profile

Cách 1: Trong biến môi trường

Windows:


$env:SPRING_PROFILES_ACTIVE="dev"

Linux/Mac:


export SPRING_PROFILES_ACTIVE=dev

Cách 2: Trong IDE

VM options:


-Dspring.profiles.active=dev

6. Build project

Trong thư mục project:


mvn clean install

Nếu build thành công sẽ thấy:

BUILD SUCCESS

7. Start project

Chạy trong IDE

Trong IntelliJ:

  1. Mở project

  2. Mở file:


DemoApplication.java
  1. Nhấn nút Run


8. Kiểm tra project chạy thành công

Sau khi start, kiểm tra log:

Started DemoApplication in 5.123 seconds

Mở trình duyệt:


http://localhost:8080

Hoặc gọi API:


http://localhost:8080/api/v1/health

Chương 2: Hướng dẫn phát triển (Developer)

Chương này mô tả cấu trúc source code, quy ước coding, cách tạo module mới, cách viết API/UI, quy trình commit/branch, và cách chạy/debug dự án trong quá trình phát triển. Mục tiêu giúp developer hiểu kiến trúc và đóng góp code đúng chuẩn.

Chương 2: Hướng dẫn phát triển (Developer)

Luồng Xử Lý Dịch Vụ API Tích Hợp Kafka (Kafka API Processing Flow)

1.1. Mục Tiêu

Tài liệu này mô tả luồng xử lý chuẩn của dịch vụ Loadbalance_kafka trên nền tảng WSO2 Micro Integrator (WSO2 MI), từ khi Client gửi request đến khi dữ liệu được xử lý và phản hồi về. Mục tiêu:


1.2. Phạm Vi Áp Dụng


1.3. Tổng Quan Luồng Xử Lý


Client (HTTP POST)
KafkaProducerApi (/kafka-producer)
kafkaTransport.init (KafkaConnection)
Kafka Topic: test_topic_01
Inbound Endpoint (KafkaMessageConsumer)
inboundSequence (Load_balance_example-inboundSequence)
Backend API (POST http://192.168.0.133:8080/api/v1/plannings)
payloadFactory (Đóng gói lại kết quả)
Kafka Topic: processed_topic

📸 [Ảnh minh họa] — Chụp màn hình sơ đồ luồng WSO2 MI Studio hoặc Kafka Manager


1.4. Các Bước Xử Lý Chi Tiết


Bước 1. Client Gửi Request

Client gọi API qua HTTP:

http
POST http://<wso2mi-host>:8290/kafka-producer
Content-Type: application/json
Authorization: Bearer <jwt_token>

{
"field1": "value1",
"field2": "value2"
}

Quy định:

📸 [Ảnh minh họa] — Ảnh chụp Postman/Curl gửi request thành công


Bước 2. KafkaProducerApi Nhận Request

File: 


KafkaProducerApi.xml

 

API nhận request và bắt đầu luồng xử lý inSequence.

xml
<api context="/kafka-producer" name="KafkaProducerApi">
<resource methods="POST">
<inSequence>
<!-- Log request vào -->
<log level="custom">
<property name="API_NAME" value="KafkaProducerApi"/>
<property name="STATUS" value="Processing incoming message..."/>
<property name="PAYLOAD" expression="json-eval($)"/>
</log>
...
</inSequence>
<faultSequence>
<!-- Trả lỗi nếu Kafka fail -->
</faultSequence>
</resource>
</api>

Log ghi nhận:

📸 [Ảnh minh họa] — Ảnh log WSO2 MI Console hoặc file log khi request vào


Bước 3. Khởi Tạo Kết Nối Kafka (kafkaTransport.init)

File: 


KafkaProducerApi.xml (trong inSequence)

 

xml
<kafkaTransport.init>
<name>KafkaConnection</name>
<bootstrapServers>hdp-master:9092</bootstrapServers>
<keySerializerClass>org.apache.kafka.common.serialization.StringSerializer</keySerializerClass>
<valueSerializerClass>org.apache.kafka.common.serialization.StringSerializer</valueSerializerClass>
<acks>all</acks>
<requestTimeout>10000</requestTimeout>
<maxBlock>5000</maxBlock>
</kafkaTransport.init>

Giải thích từng trường:

Trường Giá trị Mô tả
name KafkaConnection Tên kết nối dùng để Sequence tham chiếu (configKey).
bootstrapServers hdp-master:9092 Địa chỉ Kafka Broker.
keySerializerClass StringSerializer Class serialize Key của message (kiểu chuỗi).
valueSerializerClass StringSerializer Class serialize Value của message (kiểu chuỗi).
acks all Broker phải xác nhận ghi đủ trên mọi replica mới coi là thành công.
requestTimeout 10000 Timeout cho mỗi request gửi lên Kafka (ms).
maxBlock 5000 Thời gian tối đa chờ nếu buffer đầy hoặc Kafka chưa sẵn sàng (ms).

Bước 4. Publish Message vào Kafka

File: 


KafkaProducerApi.xml

 

xml
<kafkaTransport.publishMessages>
<topic>test_topic_01</topic>
</kafkaTransport.publishMessages>

Giải thích:


Bước 5. Trả Response Cho Client (Producer)

xml
<payloadFactory media-type="json">
<format>{"success": "true", "message": "Hồ sơ đã được gửi thành công"}</format>
</payloadFactory>
<respond/>

Sau khi publish xong, API trả ngay về Client mà không chờ xử lý phía Consumer.

Response thành công:

json
{
"success": "true",
"message": "Hồ sơ đã được gửi thành công"
}

Response thất bại (faultSequence):

json
{
"status": "Failed",
"error": "<error_message>"
}

Bước 6. Inbound Endpoint Lắng Nghe Kafka

File: 


Load_balance_example.xml

 

Đây là thành phần chạy ngầm liên tục, tự động poll message từ Kafka.

xml
<inboundEndpoint
name="Load_balance_example"
class="org.wso2.carbon.inbound.kafka.KafkaMessageConsumer"
sequence="Load_balance_example-inboundSequence"
onError="Load_balance_example-inboundErrorSequence">

Toàn bộ tham số cấu hình:

Tham số Giá trị Mô tả
interval 100 Khoảng cách giữa 2 lần poll (ms).
sequential false false = đa luồng, tận dụng tối đa CPU.
coordination true Tránh consume trùng khi chạy cluster nhiều node.
suspend false false = khởi động ngay, không dừng.
bootstrap.servers hdp-master:9092 Địa chỉ Kafka Broker kết nối đến.
topic.name test_topic_01 Topic mà Inbound Endpoint theo dõi.
group.id group1 Tên Consumer Group. Nhiều node cùng group sẽ chia partition.
contentType application/json Định dạng nội dung message nhận về.
poll.timeout 5000 Thời gian chờ khi không có dữ liệu (ms).
key.deserializer StringDeserializer Class giải mã Key của message.
value.deserializer StringDeserializer Class giải mã Value của message.
avro.use.logical.type.converters false Không dùng Avro logical type.
enable.auto.commit true Tự commit offset sau khi đọc xong.
auto.commit.interval.ms 5000 Chu kỳ tự commit offset (ms).
auto.offset.reset latest Bắt đầu đọc từ message mới nhất nếu không có offset cũ.
exclude.internal.topics true Bỏ qua các topic nội bộ của Kafka.
check.crcs true Kiểm tra tính toàn vẹn dữ liệu (CRC).
partition.assignment.strategy RangeAssignor Chiến lược phân chia partition cho consumer.
max.poll.interval.ms 300000 Thời gian tối đa xử lý một đợt poll (5 phút).
max.poll.records 500 Số message tối đa lấy về mỗi đợt poll.
fetch.max.wait.ms 500 Thời gian chờ tối đa fetch từ broker nếu chưa đủ data (ms).
receive.buffer.bytes 65536 Kích thước buffer nhận dữ liệu TCP (64KB).
send.buffer.bytes 131072 Kích thước buffer gửi dữ liệu TCP (128KB).
request.timeout.ms 305000 Timeout cho mỗi request gửi đến Broker (ms).
reconnect.backoff.ms 50 Thời gian chờ trước khi kết nối lại sau lỗi (ms).
retry.backoff.ms 100 Thời gian chờ trước khi thử lại request thất bại (ms).
connections.max.idle.ms 540000 Đóng kết nối nếu idle quá thời gian này (9 phút).
security.protocol PLAINTEXT Giao thức bảo mật (tắt mã hóa).
metrics.num.samples 2 Số mẫu dùng để tính metrics.
metrics.recording.level INFO Mức độ ghi metrics.
metrics.sample.window.ms 30000 Cửa sổ thời gian lấy mẫu metrics (30 giây).

Bước 7. Sequence Xử Lý Message Nhận Được

File: 


Load_balance_example-inboundSequence.xml

 

7.1. Lấy Payload từ Kafka

xml
<property name="PAYLOAD" expression="json-eval($)" />
<log level="custom">
<property name="STATUS" value="[KafkaConsumer] Message du lieu nhan duoc"/>
<property name="TOPIC" value="test_topic_01"/>
<property name="PAYLOAD" expression="get-property('PAYLOAD')"/>
</log>

Các property được tạo:

Property Giá trị / Expression Mô tả
PAYLOAD json-eval($) Nội dung JSON toàn bộ của message từ Kafka.

7.2. Gọi Backend API

xml
<header name="Authorization" scope="transport"
value="Bearer eyJhbGciOiJIUzI1NiJ9..."/>
<property name="Content-Type" value="application/json" scope="transport"/>

<call>
<endpoint>
<address uri="http://192.168.0.133:8080/api/v1/plannings">
<suspendOnFailure>
<initialDuration>1000</initialDuration>
<progressionFactor>1.0</progressionFactor>
<maximumDuration>60000</maximumDuration>
</suspendOnFailure>
</address>
</endpoint>
</call>

Thông tin endpoint:

Trường Giá trị Mô tả
uri http://192.168.0.133:8080/api/v1/plannings URL Backend API nhận dữ liệu.
Authorization Bearer <jwt_token> Token xác thực gửi kèm header.
Content-Type application/json Định dạng body gửi đến Backend.
initialDuration 1000 Chờ 1 giây trước khi retry khi lỗi (ms).
progressionFactor 1.0 Hệ số tăng thời gian retry (1.0 = không tăng).
maximumDuration 60000 Thời gian chờ tối đa khi retry (60 giây).

7.3. Lưu Response từ Backend

xml
<property name="API_RESPONSE" expression="json-eval($)" scope="default"/>
<property name="HTTP_STATUS" expression="$axis2:HTTP_SC" scope="default"/>

Các property lưu kết quả:

Property Expression Mô tả
API_RESPONSE json-eval($) Toàn bộ JSON response từ Backend API.
HTTP_STATUS $axis2:HTTP_SC Mã HTTP status code (200, 500...) của Backend.
RESPONSE_SIZE fn:string-length(...) Độ dài chuỗi JSON của response (dùng để log).

7.4. Đóng Gói Lại Dữ Liệu (payloadFactory)

xml
<payloadFactory media-type="json" template-type="default">
<format>{
"eventType": "transaction",
"source": "bank-transactions",
"data": ${payload}
}</format>
</payloadFactory>

Cấu trúc JSON đầu ra:

Trường Giá trị Kiểu Mô tả
eventType transaction String (cố định) Phân loại sự kiện.
source bank-transactions String (cố định) Nguồn gốc dữ liệu.
data ${payload} Object (JSON động) Toàn bộ message gốc nhận từ Kafka.

7.5. Publish Kết Quả vào Topic Phản Hồi

xml
<kafkaTransport.publishMessages configKey="KafkaConnection">
<topic>processed_topic</topic>
<partitionNo>0</partitionNo>
<forwardExistingHeaders>None</forwardExistingHeaders>
<customHeaders>[]</customHeaders>
</kafkaTransport.publishMessages>

Các trường publish:

Trường Giá trị Mô tả
configKey KafkaConnection Tham chiếu đến Local Entry kết nối Kafka.
topic processed_topic Topic nhận kết quả đã xử lý.
partitionNo 0 Partition cố định (0 = partition đầu tiên).
forwardExistingHeaders None Không chuyển tiếp header gốc.
customHeaders [] Không thêm header tuỳ chỉnh.

Bước 8. Local Entry - Cấu Hình Kết Nối Kafka Tái Sử Dụng

File: 


KafkaConnection.xml

 

xml
<localEntry key="KafkaConnection">
<kafkaTransport.init>
<connectionType>KAFKA</connectionType>
<bootstrapServers>hdp-master:9092</bootstrapServers>
<keySerializerClass>...StringSerializer</keySerializerClass>
<valueSerializerClass>...StringSerializer</valueSerializerClass>
<poolingEnabled>false</poolingEnabled>
<name>KafkaConnection</name>
</kafkaTransport.init>
</localEntry>

Giải thích:

Trường Giá trị Mô tả
key KafkaConnection Tên Local Entry dùng để gọi qua configKey.
connectionType KAFKA Loại kết nối.
bootstrapServers hdp-master:9092 Kafka Broker.
poolingEnabled false Không dùng connection pool.

Bước 9. Xử Lý Lỗi (Error Sequence)

File: 


Load_balance_example-inboundErrorSequence.xml

 

xml
<sequence name="Load_balance_example-inboundErrorSequence">
<log category="INFO" logMessageID="false" logFullPayload="false">
<message>Lỗi khi lắng nghe lấy dữ liệu từ kafka</message>
</log>
</sequence>

Khi Inbound Endpoint bị lỗi (không parse được message, mất kết nối...), WSO2 MI sẽ kích hoạt sequence lỗi này.

⚠️ Ghi chú: Hiện tại sequence lỗi chỉ log thông báo. Cần bổ sung logic retry hoặc dead-letter queue cho production.


1.5. Luồng Phát Triển & Deploy


Dev Code XML → Build CAR → Deploy lên WSO2 MI

Bước 10. Build Project

bash
mvn clean install

Output: File Loadbalance_kafka_1.0.0.car trong thư mục target/.

Bước 11. Deploy lên WSO2 MI Server

Tự động qua Maven:

bash
mvn deploy

Hoặc copy file .car vào thư mục <WSO2MI_HOME>/repository/deployment/server/carbonapps/.

Cấu hình server deploy (trong 


pom.xml):

 

Trường Giá trị Mô tả
serverUrl http://192.168.0.167:9201 địa chỉ WSO2 MI Management API.
userName adminvp Tài khoản admin MI.
serverType mi Loại server WSO2.
operation deploy Hành động thực hiện.

📸 [Ảnh minh họa] — Ảnh chụp Deploy thành công trong WSO2 MI Dashboard


1.6. Sơ Đồ Tổng Thể API Lifecycle


Client POST Request
KafkaProducerApi (context: /kafka-producer)
kafkaTransport.init (KafkaConnection Local Entry)
Publish → Kafka: test_topic_01
↓ ↓ (response lại Client ngay)
↓ {"success": "true", ...}
Inbound Endpoint (poll interval: 100ms)
inboundSequence
Call Backend: POST /api/v1/plannings
payloadFactory: {eventType, source, data}
Publish → Kafka: processed_topic
↓ (error path)
inboundErrorSequence → Log lỗi

1.7. Phụ Lục: Cấu Hình Build (


pom.xml)

Tham số Giá trị Mô tả
artifactId Loadbalance_kafka Tên artifact Maven.
groupId com.microintegrator.projects Group package.
version 1.0.0 Phiên bản hiện tại.
project.runtime.version 4.4.0 Phiên bản WSO2 MI.
dockerfile.base.image wso2/wso2mi:4.4.0 Docker base image.
dockerfile.name loadbalance_kafka:1.0.0 Tên Docker image output.
mi-inbound-kafka 2.0.6 Phiên bản connector nhận message từ Kafka.
mi-connector-kafka 3.3.10 Phiên bản connector gửi message lên Kafka.
mi-connector-http 0.1.14 Phiên bản connector gọi HTTP.
Chương 2: Hướng dẫn phát triển (Developer)

1.Cấu trúc dự án

1.1. Mục tiêu chương

Chương này quy định cấu trúc dự án chuẩn và phạm vi quản lý artifact giữa Core Team và các đơn vị Partner, nhằm:


1.2. Khái niệm và phạm vi áp dụng

Tài liệu này được áp dụng cho:

1.3. Cấu trúc dự án chuẩn

Cấu trúc dự án được tổ chức theo tiêu chuẩn nhằm phân tách rõ ràng giữa cấu hình hệ thống, tài nguyên tích hợp và logic nghiệp vụ, đảm bảo tính nhất quán và khả năng mở rộng trong quá trình phát triển.

image.png


Ý Nghĩa Từng thư mục

* Root level

.mvn, mvnw, pom.xml

 Project build bằng Maven

  • pom.xml → quản lý dependency, plugin build WSO2

  • .mvn → config Maven wrapper


+ deployment/

 Cấu hình runtime & deploy

  • deployment.toml → config server (port, datasource, kafka...)

  • docker/ → chạy container (nếu có)

  • libs/ → thêm JAR custom (Kafka client, connector...)


+ docs/

Tài liệu project (thiết kế, API spec...)


 * Source chính

+ src/main/java

 Nếu có custom Java (hiện tại bạn chưa dùng)


+ src/main/wso2mi

Đây là toàn bộ logic integration


+ artifacts/


+ apis/

 API nhận request từ client

  • KafkaProducerApi.xml
    → API nhận request → đẩy vào Kafka (producer)

  • PlanningDirectApi.xml
    → API gọi trực tiếp backend (không qua Kafka)


+ inbound-endpoints/

Kafka Consumer (cực kỳ quan trọng)

  • Load_balance_example.xml
    → Lắng nghe Kafka topic
    → Khi có message → trigger sequence xử lý

Đây chính là chỗ load balancing bằng Kafka


+ sequences/

 Logic xử lý nghiệp vụ

  • Load_balance_example-inboundSequence.xml
    → Xử lý message lấy từ Kafka
    → gọi backend

  • Load_balance_example-inboundErrorSequence.xml
    → xử lý lỗi khi fail


+ endpoints/

 Định nghĩa backend service

Ví dụ:

  • HTTP endpoint

  • REST API backend


+ local-entries/

Config dùng chung

  • KafkaConnection.xml
    → cấu hình Kafka broker (host, port, topic...)


+ message-stores/

 Lưu message (queue trung gian)

 Dùng khi:

  • retry

  • đảm bảo không mất dữ liệu


+ message-processors/

 Xử lý message từ store

 Ví dụ:

  • retry gửi lại

  • xử lý batch


+ proxy-services/

 Proxy service (SOAP/HTTP)

 Thường dùng khi:

  • wrap service cũ

  • expose service trung gian


+ data-services/

 Kết nối database

 Tạo API CRUD trực tiếp DB


+ data-sources/

 Cấu hình kết nối DB


+ tasks/

 Job chạy định kỳ (cron job)


+ templates/

 Template tái sử dụng

 Giúp:

  • tránh lặp code sequence / endpoint


+ resources/

 Tài nguyên bổ trợ

  • conf/ → config runtime (properties)

  • connectors/ → Kafka connector JAR

  • api-definitions/ → Swagger/OpenAPI

  • metadata/ → metadata của artifact




1.4. Quy định chính

1.4.1. Phân quyền quản lý artifact

Khu vực Đơn vị quản lý Quyền chỉnh sửa
deployment/** Core Team Partner không được phép chỉnh sửa
artifacts/local-entries/** Core Team Partner không được phép chỉnh sửa
resources/conf/** Core Team Partner không được phép chỉnh sửa
resources/connectors/** Core Team Partner không được phép chỉnh sửa
artifacts/apis/** Core Team + Partner Được phép phát triển
artifacts/sequences/** Core Team + Partner Được phép phát triển
artifacts/inbound-endpoints/** Core Team + Partner Được phép phát triển
artifacts/endpoints/** Core Team + Partner Được phép phát triển

1.4.2. Quy định bắt buộc

Partner không được phép chỉnh sửa trực tiếp các khu vực sau:

  • deployment/**

  • artifacts/local-entries/**

  • resources/conf/**

  • resources/connectors/**

Mọi thay đổi liên quan đến các khu vực trên phải tuân theo quy trình kiểm soát của Core Team, bao gồm:

  1. Tạo yêu cầu thay đổi (Core Change Request)

  2. Được Core Team xem xét và đánh giá

  3. Được Core Team phê duyệt và thực hiện thay đổi


1.5. Cách thực hiện / Quy trình

1.5.1. Quy trình thay đổi artifact dùng chung

Bước 1: Partner xác định nhu cầu thay đổi đối với các khu vực do Core Team quản lý.

Bước 2: Tạo ticket trên hệ thống quản lý công việc (Jira, Redmine hoặc YouTrack) với tiêu đề:

[CORE CHANGE REQUEST] <Mô tả ngắn gọn thay đổi>

Ví dụ:
[CORE CHANGE REQUEST] Thêm Kafka topic mới vào KafkaConnection local-entry


Luồng xử lý

Partner tạo ticket → Core Team review → Cập nhật trạng thái → Core thực hiện → Partner cập nhật code


Trạng thái ticket

Trạng thái Ý nghĩa
Open Ticket được tạo mới
Under Review Core Team đang đánh giá
Approved Được chấp thuận thực hiện
Rejected Bị từ chối (kèm lý do)
Implemented Core Team đã hoàn thành thay đổi
Merged Đã merge vào nhánh develop

Bước 3: Core Team thực hiện đánh giá dựa trên các tiêu chí:

  • Phạm vi ảnh hưởng đến các luồng hiện tại

  • Rủi ro liên quan đến cấu hình (ví dụ: Kafka, endpoint, connection)

  • Tính cần thiết và mức độ ảnh hưởng đến hệ thống


Bước 4: Nếu thay đổi được chấp thuận:

  • Core Team thực hiện cập nhật

  • Merge vào nhánh develop


Bước 5: Partner cập nhật code mới và tiếp tục phát triển.


1.6. Ví dụ minh họa

1.6.1. Trường hợp hợp lệ

Tình huống: Bổ sung xử lý lỗi cho inbound sequence Kafka

artifacts/sequences/Load_balance_example-inboundSequence.xml
artifacts/sequences/Load_balance_example-inboundErrorSequence.xml

→ Được phép chỉnh sửa trực tiếp và tạo Pull Request.


Tình huống: Thêm API mới để nhận dữ liệu

artifacts/apis/PlanningDirectApi.xml

→ Được phép tạo mới hoặc chỉnh sửa, sau đó tạo Pull Request để Core Team review.


1.6.2. Trường hợp không hợp lệ

Tình huống: Thêm Kafka topic vào cấu hình kết nối

artifacts/local-entries/KafkaConnection.xml

→ Không được phép chỉnh sửa trực tiếp.

Phải thực hiện theo quy trình:

  1. Tạo ticket:
    [CORE CHANGE REQUEST] Thêm topic planning_topic vào KafkaConnection

  2. Chờ Core Team phê duyệt

  3. Core Team thực hiện thay đổi và merge


Tình huống: Thay đổi cấu hình Kafka bootstrap server

deployment/deployment.toml
resources/conf/config.properties

→ Không được phép chỉnh sửa trực tiếp. Phải tạo Core Change Request.


1.7. Checklist áp dụng

Trước khi commit hoặc tạo Pull Request, cần đảm bảo:

  • Artifact nằm trong các thư mục được phép chỉnh sửa (artifacts/apis/, artifacts/sequences/, artifacts/inbound-endpoints/, artifacts/endpoints/)

  • Không chỉnh sửa artifacts/local-entries/**

  • Không chỉnh sửa deployment/**

  • Không chỉnh sửa resources/conf/**

  • Không chỉnh sửa resources/connectors/**

  • Pull Request đã được Core Team review (nếu ảnh hưởng đến luồng chung)

  • Đã kiểm thử luồng Kafka producer/consumer sau khi thay đổi (nếu có)


Tài liệu này thuộc phạm vi quản lý của Core Team. Mọi thay đổi nội dung phải được Core Team phê duyệt.


Chương 2: Hướng dẫn phát triển (Developer)

2. API Design & Response Standard

2.1. Mục Tiêu Chương

Chương này quy định cấu trúc chuẩn của mỗi integration flow và trách nhiệm của từng artifact layer nhằm:

2.2. Khái Niệm / Phạm Vi Áp Dụng

Quy tắc này áp dụng cho:

Cấu Trúc Artifact Chuẩn

Mỗi integration flow gồm các thành phần sau:image.png

Ý Nghĩa Từng Artifact Layer
Layer Artifact Vai trò Ví dụ
API Layer apis/*.xml Nhận request từ client và trả response

KafkaProducerApi.xml

PlanningDirectApi.xml
Mediation Layer sequences/*-inboundSequence.xml Xử lý logic, điều phối luồng

Load_balance_example-inboundSequence.xml
Error Layer sequences/*-inboundErrorSequence.xml Xử lý và cô lập lỗi

Load_balance_example-inboundErrorSequence.xml
Consumer Layer inbound-endpoints/*.xml Nhận message từ Kafka topic

Load_balance_example.xml
Connection Layer local-entries/*.xml Cấu hình kết nối dùng chung

KafkaConnection.xml
Endpoint Layer endpoints/*.xml Định nghĩa backend endpoint Backend API address

2.3. Quy Định Chính

Trách Nhiệm Từng Layer

API Layer (apis/)

Mediation Layer (sequences/*-inboundSequence.xml)

Error Layer (sequences/*-inboundErrorSequence.xml)

Consumer Layer (inbound-endpoints/)

Connection Layer (local-entries/)

Quy Tắc Bắt Buộc

 API Layer không được chứa logic nghiệp vụ phức tạp

Mọi response trả về client phải theo cấu trúc JSON chuẩn

Error sequence phải luôn publish message vào error_topic

Mediation sequence phải phân loại HTTP status (2xx / 4xx / 5xx)

Cấu Trúc Response Chuẩn

Thành công:

{
  "success": true,
  "message": "Hồ sơ đã được gửi thành công"
}

Thành công kèm data:

{
  "eventType": "transaction",
  "source": "<sequence-name>",
  "data": { ... }
}

 

Lỗi từ API Layer:

{
  "success": false,
  "httpStatus": "400",
  "error": { ... }
}

 

Lỗi kỹ thuật (Error Sequence ghi vào error_topic):

{
  "errorSource": "Load_balance_example-inboundEndpoint",
  "sourceTopic": "test_topic_01",
  "errorType": "KAFKA_CONSUME_ERROR",
  "errorCode": "...",
  "errorMessage": "...",
  "originalPayload": { ... },
  "timestamp": "..."
}

 

2.4. Cách Thực Hiện / Quy Trình

Quy Trình Phát Triển Một Integration Flow Mới

Bước 1: Khai báo Connection (nếu chưa có)

Tạo hoặc tái sử dụng  local-entries/KafkaConnection.xml.

Chỉ Core Team thực hiện bước này.

Bước 2: Tạo Inbound Endpoint

Tạo file inbound-endpoints/<flow-name>.xml:

Bước 3: Tạo Error Sequence

Tạo file sequences/<flow-name>-inboundErrorSequence.xml:

Bước 4: Tạo Inbound Sequence

Tạo file sequences/<flow-name>-inboundSequence.xml:

Bước 5: Tạo API (nếu cần điểm vào HTTP)

Tạo file apis/<flow-name>Api.xml:

ho-so-core-flow (1).jpg

 

2.5. Ví Dụ Minh Họa

Ví Dụ Đúng — Inbound Sequence phân loại HTTP Status

<!-- Thành công: publish vào processed_topic -->
<filter xpath="get-property('HTTP_STATUS') = '200' or get-property('HTTP_STATUS') = '201'">
    <then>
        <log level="custom">
            <property name="STATUS" value="[KafkaConsumer][SUCCESS] Backend xử lý thành công"/>
        </log>
        <payloadFactory media-type="json" template-type="default">
            <format>{
                "eventType": "transaction",
                "source": "Load_balance_example-inboundSequence",
                "data": ${payload}
            }</format>
        </payloadFactory>
        <kafkaTransport.publishMessages configKey="KafkaConnection">
            <topic>processed_topic</topic>
        </kafkaTransport.publishMessages>
    </then>
    <else>
        <!-- Lỗi: publish vào error_topic -->
        <kafkaTransport.publishMessages configKey="KafkaConnection">
            <topic>error_topic</topic>
        </kafkaTransport.publishMessages>
    </else>
</filter>

Đúng vì:

Ví Dụ Đúng — Error Sequence đóng gói lỗi chuẩn

<sequence name="Load_balance_example-inboundErrorSequence">
    <!-- Bước 1: Lưu thông tin lỗi -->
    <property name="ERROR_CODE"      expression="get-property('ERROR_CODE')"    scope="default"/>
    <property name="ERROR_MESSAGE"   expression="get-property('ERROR_MESSAGE')" scope="default"/>
    <property name="ORIGINAL_PAYLOAD" expression="json-eval($)"                 scope="default"/>

    <!-- Bước 2: Log chi tiết -->
    <log level="custom">
        <property name="STATUS"         value="[KafkaConsumer][ERROR] Lỗi khi xử lý message"/>
        <property name="ERROR_CODE"     expression="get-property('ERROR_CODE')"/>
        <property name="ERROR_MESSAGE"  expression="get-property('ERROR_MESSAGE')"/>
    </log>

    <!-- Bước 3: Đóng gói payload lỗi -->
    <payloadFactory media-type="json">
        <format>{
            "errorSource": "Load_balance_example-inboundEndpoint",
            "sourceTopic": "test_topic_01",
            "errorType":   "KAFKA_CONSUME_ERROR",
            "errorCode":   "$1",
            "errorMessage":"$2",
            "originalPayload": $3
        }</format>
    </payloadFactory>

    <!-- Bước 4: Publish vào error_topic -->
    <kafkaTransport.publishMessages configKey="KafkaConnection">
        <topic>error_topic</topic>
    </kafkaTransport.publishMessages>
</sequence>

 

 Ví Dụ Sai — Không phân loại HTTP Status

<!-- SAI: Không kiểm tra status, trả về luôn -->
<call>
    <endpoint>
        <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
    </endpoint>
</call>
<respond/>

Sai vì:

 Ví Dụ Sai — API trả về raw data không theo chuẩn

<!-- SAI: Không wrap response theo chuẩn -->
<payloadFactory media-type="json">
    <format>{"data": "ok"}</format>
</payloadFactory>
<respond/>

Sai vì:

2.6. Checklist Áp Dụng

Trước khi commit integration flow mới:

Tài liệu này thuộc phạm vi quản lý của Core Team — mọi thay đổi phải được Core Team phê duyệt.

Chương 2: Hướng dẫn phát triển (Developer)

3.Cơ chế xác thực (JWT/Bearer)

3.1. Mục Tiêu Chương

Chương này quy định chuẩn xác thực và nguyên tắc thiết kế bảo mật nhằm:

3.2. Khái Niệm / Phạm Vi Áp Dụng

Quy định này áp dụng cho:

Kiến Trúc Xác Thực Tổng Thể

image.png

Ba Tầng Xác Thực

Tầng Thành phần Vai trò xác thực
Cấp token WSO2 Identity Server (IS) OAuth2 Authorization Server, cấp JWT/Access Token
Validate token WSO2 API Manager (APIM) Xác thực token tại Gateway trước khi route vào MI
Forward token WSO2 Micro Integrator (MI) Set Authorization: Bearer header khi gọi backend

3.3. Quy Định Chính

3.3.1. Quy Tắc Đặt Tên Artifact

Các artifact liên quan xác thực phải đặt tên theo quy ước thống nhất:

Thành phần Quy tắc đặt tên Ví dụ
REST API <Name>Api.xml

KafkaProducerApi.xml

PlanningDirectApi.xml
Inbound Sequence <flow>-inboundSequence.xml

Load_balance_example-inboundSequence.xml
Error Sequence <flow>-inboundErrorSequence.xml

Load_balance_example-inboundErrorSequence.xml
Inbound Endpoint <flow>.xml

Load_balance_example.xml
Connection Entry <Name>Connection.xml

KafkaConnection.xml
Backend Endpoint <Name>Endpoint.xml PlanningBackendEndpoint.xml

3.3.2. Quy Tắc Đặt Property Trong Sequence

Dùng tên property nhất quán khi xử lý token/header:

<!-- Đúng: Tên property rõ ràng, đúng chuẩn -->
<property name="PAYLOAD"          expression="json-eval($)"    scope="default"/>
<property name="HTTP_STATUS"      expression="$axis2:HTTP_SC"  scope="default"/>
<property name="ERROR_CODE"       expression="get-property('ERROR_CODE')"    scope="default"/>
<property name="ERROR_MESSAGE"    expression="get-property('ERROR_MESSAGE')" scope="default"/>

<!-- Sai: Tên không mô tả ý nghĩa -->
<property name="x"    expression="json-eval($)"   scope="default"/>
<property name="code" expression="$axis2:HTTP_SC" scope="default"/>

3.3.3. Quy Định Về Logging Xác Thực

Không được bỏ qua bước log khi xử lý request:

<!-- Sai: Không log trạng thái -->
<call>
    <endpoint>
        <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
    </endpoint>
</call>

Phải log đầy đủ các bước:

<!-- Đúng: Log đầy đủ theo chuẩn dự án -->
<log level="custom">
    <property name="API_NAME" value="PlanningDirectApi"/>
    <property name="STATUS"   value="Nhận request, gọi thẳng Backend..."/>
    <property name="PAYLOAD"  expression="get-property('PAYLOAD')"/>
</log>

 

3.3.4. Quy Định Về Token / Header Xác Thực

Token dùng để gọi backend phải được set từ local-entries hoặc config.properties — không hardcode trong Sequence:

<!-- SAI: Hardcode token trong Sequence -->
<header name="Authorization" scope="transport"
    value="Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9..."/>
<!-- ĐÚNG: Lấy token từ property/config -->
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"
    scope="default"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>

3.4. Nguyên Lý Tách Biệt Trách Nhiệm (Separation of Concerns)

Tương đương nguyên lý SOLID áp dụng trong hệ thống WSO2:

3.4.1. S — Mỗi Artifact Một Trách Nhiệm

Mỗi artifact chỉ được thực hiện một nhiệm vụ:

Artifact Chỉ được làm
apis/ Nhận HTTP request, set header auth, gọi backend/Kafka, trả response
sequences/*-inboundSequence.xml Điều phối luồng message từ Kafka đến backend
sequences/*-inboundErrorSequence.xml Ghi nhận lỗi và publish vào error_topic
inbound-endpoints/ Kết nối Kafka, subscribe topic
local-entries/ Lưu thông tin kết nối và cấu hình dùng chung

Sai — Error logic nằm trong inbound sequence:

<!-- SAI: Inbound sequence tự xử lý lỗi kỹ thuật -->
<sequence name="myFlow-inboundSequence">
    <call>...</call>
    <property name="ERROR_CODE" expression="get-property('ERROR_CODE')"/>
    <kafkaTransport.publishMessages configKey="KafkaConnection">
        <topic>error_topic</topic>
    </kafkaTransport.publishMessages>
    <!-- Đây là nhiệm vụ của inboundErrorSequence -->
</sequence>

Đúng — Error sequence riêng biệt:

<inboundEndpoint
    sequence="myFlow-inboundSequence"
    onError="myFlow-inboundErrorSequence">

3.4.2. O — Mở Rộng Không Sửa Artifact Cũ

Khi thêm topic/luồng mới, tạo artifact mới — không sửa sequence của luồng khác:

<!-- Thêm luồng mới: tạo file riêng -->
<!-- planning_topic-inboundSequence.xml -->
<!-- planning_topic-inboundErrorSequence.xml -->
<!-- planning_topic.xml (inbound endpoint) -->

3.4.3. L — Consumer Tuân Thủ Contract Sequence

Inbound Endpoint phải trỏ đúng sequence và error sequence đã khai báo:

<!-- Sai: onError trỏ sai sequence -->
<inboundEndpoint name="Load_balance_example"
    sequence="Load_balance_example-inboundSequence"
    onError="someOtherSequence"/>  <!-- không tồn tại -->
<!-- Đúng -->
<inboundEndpoint name="Load_balance_example"
    sequence="Load_balance_example-inboundSequence"
    onError="Load_balance_example-inboundErrorSequence"/>

3.4.4. I — Tách Riêng Local Entry Theo Chức Năng

Không nhét tất cả cấu hình vào một local entry:

<!-- Sai: Một local entry chứa tất cả -->
<localEntry key="AllConfig">
    <!-- Kafka config, backend URL, token, ... -->
</localEntry>

<!-- Đúng: Tách riêng -->
<localEntry key="KafkaConnection">...</localEntry>
<localEntry key="BackendConfig">...</localEntry>

 

3.4.5. D — Phụ Thuộc Config, Không Hardcode

Sequence phụ thuộc vào local-entries và config.properties — không hardcode URL hay token:

<!-- Sai -->
<address uri="http://192.168.0.133:8080/api/v1/plannings"/>

<!-- Đúng -->
<address uri="{$ctx:backend.planning.url}"/>

3.5. Luồng Xác Thực Chuẩn Trong Hệ Thống

3.5.1. Luồng Chuẩn Từ Client Đến Backend

Client
  → [1] Lấy token từ WSO2 IS (/oauth2/token)
  → [2] Gọi API qua WSO2 APIM với Bearer token
  → [3] APIM validate token với WSO2 IS (introspect / JWT verify)
  → [4] APIM route request vào WSO2 MI
  → [5] MI set header Authorization khi gọi backend
  → [6] Backend (Spring Boot) validate JWT

3.5.2. Luồng Kafka Consumer Không Đi Qua APIM

Kafka Topic (test_topic_01)
  → [1] MI Inbound Endpoint consume message
  → [2] MI Inbound Sequence xử lý
  → [3] MI set header Authorization: Bearer <token>
  → [4] MI gọi Backend API
  → [5] Backend validate JWT
  → [6] Kết quả: published_topic hoặc error_topic

Luồng Kafka consumer bypass APIM Gateway — token phải được quản lý thủ công trong MI (lấy từ IS hoặc dùng service account token).

3.5.3. Không Được Phép

<!-- Sai: Gọi backend không có Authorization header -->
<call>
    <endpoint>
        <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
    </endpoint>
</call>

<!-- Sai: API tại APIM không bật Security Scheme -->
<!-- Mọi API publish lên APIM đều phải bật OAuth2 hoặc API Key -->

3.5.4. Luồng Token Trong Kafka Consumer (Chuẩn)

<!-- Bước 1: Lấy token từ config -->
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"
    scope="default"/>

<!-- Bước 2: Set Authorization header -->
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>
<property name="Content-Type" value="application/json" scope="transport"/>

<!-- Bước 3: Gọi backend -->
<call>
    <endpoint>
        <address uri="{$ctx:backend.planning.url}">
            <suspendOnFailure>
                <initialDuration>1000</initialDuration>
                <progressionFactor>2.0</progressionFactor>
                <maximumDuration>60000</maximumDuration>
            </suspendOnFailure>
        </address>
    </endpoint>
</call>

3.6. Cấu Hình Xác Thực WSO2 APIM & IS

3.6.1. WSO2 IS — Key Manager

WSO2 IS đóng vai trò Key Manager cho APIM:

Chức năng Endpoint WSO2 IS
Lấy Access Token POST /oauth2/token
Introspect Token POST /oauth2/introspect
Lấy Public Key (JWKS) GET /oauth2/jwks
Revoke Token POST /oauth2/revoke

Lấy token từ IS (client_credentials):

POST https://<IS_HOST>:9443/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(clientId:clientSecret)>

grant_type=client_credentials&scope=<scope>

 

3.6.2. WSO2 APIM — API Gateway Security

Mọi API publish lên APIM phải cấu hình:

Thuộc tính Giá trị chuẩn
Security Type OAuth2 hoặc API Key
Token Validation IS Introspection / JWT verify
Throttling Theo chính sách dự án
CORS Cấu hình theo môi trường

3.6.3. JWT Claims Chuẩn Của Hệ Thống

Token JWT cấp bởi WSO2 IS phải chứa:

{
  "sub": "admin",
  "role": "ADMIN",
  "iat": 1771917707,
  "exp": 1772004107,
  "iss": "https://<IS_HOST>:9443/oauth2/token",
  "aud": "...client_id...",
  "scope": "..."
}

3.7. Cách Thực Hiện / Quy Trình

Quy Trình Thêm Xác Thực Cho Luồng Mới

Bước 1: Đăng ký OAuth2 Application trong WSO2 IS

Bước 2: Cấu hình token trong 

backend.auth.token=<access_token_từ_IS>
backend.planning.url=http://192.168.0.133:8080/api/v1/plannings

Bước 3: Trong Sequence, đọc token từ config

<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>

Bước 4: Publish API lên APIM với OAuth2 Security

Bước 5: Test luồng xác thực end-to-end

image.png

3.8. Ví Dụ Minh Họa

Ví Dụ Sai — Sequence Không Có Authorization Header

<sequence name="myFlow-inboundSequence">
    <call>
        <endpoint>
            <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
        </endpoint>
    </call>
    <respond/>
</sequence>

Sai vì:

Ví Dụ Sai — Hardcode Token Trong Sequence

<header name="Authorization" scope="transport"
    value="Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9..."/>

Sai vì:

Ví Dụ Đúng — Sequence Xác Thực Và Gọi Backend Chuẩn

<sequence name="Load_balance_example-inboundSequence">

    <!-- 1. Lưu payload gốc -->
    <property name="PAYLOAD" expression="json-eval($)" scope="default"/>
    <log level="custom">
        <property name="STATUS"  value="[KafkaConsumer] Message nhận được từ Kafka"/>
        <property name="TOPIC"   value="test_topic_01"/>
        <property name="PAYLOAD" expression="get-property('PAYLOAD')"/>
    </log>

    <!-- 2. Set Authorization header từ config -->
    <property name="BACKEND_TOKEN"
        expression="get-property('conf:backend.auth.token')" scope="default"/>
    <header name="Authorization" scope="transport"
        expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>
    <property name="Content-Type" value="application/json" scope="transport"/>

    <!-- 3. Gọi backend -->
    <call>
        <endpoint>
            <address uri="{$ctx:backend.planning.url}">
                <suspendOnFailure>
                    <initialDuration>1000</initialDuration>
                    <progressionFactor>2.0</progressionFactor>
                    <maximumDuration>60000</maximumDuration>
                </suspendOnFailure>
            </address>
        </endpoint>
    </call>

    <!-- 4. Kiểm tra phản hồi -->
    <property name="HTTP_STATUS"   expression="$axis2:HTTP_SC"  scope="default"/>
    <property name="API_RESPONSE"  expression="json-eval($)"    scope="default"/>
    <log level="custom">
        <property name="STATUS"      value="[KafkaConsumer] Phản hồi Backend"/>
        <property name="HTTP_STATUS" expression="get-property('HTTP_STATUS')"/>
    </log>

    <!-- 5. Phân loại kết quả -->
    <filter xpath="get-property('HTTP_STATUS') = '200' or get-property('HTTP_STATUS') = '201'">
        <then>
            <kafkaTransport.publishMessages configKey="KafkaConnection">
                <topic>processed_topic</topic>
            </kafkaTransport.publishMessages>
        </then>
        <else>
            <kafkaTransport.publishMessages configKey="KafkaConnection">
                <topic>error_topic</topic>
            </kafkaTransport.publishMessages>
        </else>
    </filter>

</sequence>

 

[Chỗ này thêm ảnh: WSO2 MI log viewer — hiển thị STATUS, HTTP_STATUS sau khi gọi backend thành công]

Ví Dụ Đúng — API APIM Với OAuth2 Security

# resources/api-definitions/KafkaProducerApi.yaml
openapi: 3.0.0
info:
  title: Kafka Producer API
  version: 1.0.0
paths:
  /kafka-producer:
    post:
      summary: Publish message lên Kafka
      security:
        - OAuth2: []          # Bắt buộc xác thực OAuth2 tại APIM
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Thành công
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://<IS_HOST>:9443/oauth2/token
          scopes: {}

 

3.9. Checklist Áp Dụng

Trước khi commit hoặc tạo PR:

Naming

Token & Xác Thực

Logging

Separation of Concerns

Architecture

3.10. Cấu Trúc Chuẩn Của Một Sequence Xử Lý Nghiệp Vụ

3.10.1. Mục Tiêu

Quy định cấu trúc thống nhất của một inboundSequence nhằm:

3.10.2. Cấu Trúc Bắt Buộc

Một inboundSequence phải theo thứ tự:

1. Lưu payload gốc vào property
2. Log thông tin message nhận được
3. Set Authorization header (Bearer token)
4. Gọi backend API (<call>)
5. Lưu HTTP status và response
6. Log phản hồi backend
7. Phân loại theo HTTP status (filter)
   - 2xx → publish vào processed_topic
   - 4xx → đóng gói lỗi dữ liệu → publish vào error_topic
   - 5xx → đóng gói lỗi backend → publish vào error_topic

 

3.10.3. Quy Định Chi Tiết Từng Bước

1. Lưu payload:

<property name="PAYLOAD" expression="json-eval($)" scope="default"/>

 

2. Log input:

<log level="custom">
    <property name="STATUS"  value="[KafkaConsumer] Message nhận được từ Kafka"/>
    <property name="PAYLOAD" expression="get-property('PAYLOAD')"/>
</log>

 

3. Set header xác thực (từ config, không hardcode):

<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>

 

4. Gọi backend:

<call>
    <endpoint>
        <address uri="{$ctx:backend.planning.url}">
            <suspendOnFailure>
                <initialDuration>1000</initialDuration>
                <progressionFactor>2.0</progressionFactor>
                <maximumDuration>60000</maximumDuration>
            </suspendOnFailure>
        </address>
    </endpoint>
</call>

 

5–6. Lưu và log phản hồi:

<property name="HTTP_STATUS"  expression="$axis2:HTTP_SC"  scope="default"/>
<property name="API_RESPONSE" expression="json-eval($)"    scope="default"/>
<log level="custom">
    <property name="HTTP_STATUS"    expression="get-property('HTTP_STATUS')"/>
    <property name="RESPONSE_BODY"  expression="get-property('API_RESPONSE')"/>
</log>

 

7. Phân loại kết quả:

<filter xpath="get-property('HTTP_STATUS') = '200' or get-property('HTTP_STATUS') = '201'">
    <then>
        <!-- publish processed_topic -->
    </then>
    <else>
        <filter xpath="get-property('HTTP_STATUS') >= '400' and get-property('HTTP_STATUS') &lt; '500'">
            <then>
                <!-- 4xx: lỗi dữ liệu → error_topic -->
            </then>
            <else>
                <!-- 5xx: lỗi backend → error_topic -->
            </else>
        </filter>
    </else>
</filter>

 

3.10.4. Ví Dụ Sequence Sai

Sai vì: Không có Bearer token, không log, không phân loại HTTP status, không publish kết quả.

3.10.5. Checklist Review Sequence

Khi review một inboundSequence:

Tài liệu này thuộc phạm vi quản lý của Core Team — mọi thay đổi phải được Core Team phê duyệt.


Chương 2: Hướng dẫn phát triển (Developer)

4. Logging & Audit trong Sequence

4.1. Mục tiêu chương


Chương này quy định cách kiểm soát các thay đổi liên quan đến cấu trúc dữ liệu nhằm:

4.2. Khái niệm / phạm vi áp dụng


Quy định này áp dụng cho:

Các loại thay đổi cần kiểm soát

4.3. Quy định chính


4.3.1. Không được tự ý thay đổi các field chuẩn

Các field chuẩn hệ thống:

Quy định bắt buộc

Không được:

Các field này được coi là system fields và được dùng chung cho:


4.3.2. Thêm field mới trong module

Partner chỉ được phép thêm field khi:


4.3.3. Các thay đổi bắt buộc xin phép Core Team

Các thay đổi sau không được tự ý thực hiện:

Tất cả các trường hợp trên phải qua:

Data Model Change Request

4.4. Cách thực hiện / quy trình


Trường hợp 1: Thêm field trong module

Bước 1: Cập nhật entity trong:

modules/<module>/entity/**


Bước 2: Tạo migration script:

V<timestamp>__add_<field>_to_<table>.sql


Bước 3: Tạo Pull Request, trong PR phải có:

Bước 4: Core Team review:

Bước 5: Merge vào develop


Trường hợp 2: Thay đổi field hoặc quan hệ entity

Bước 1: Tạo ticket: các phần mềm quản lý công việc liên quan (Jira / Redmine / YouTrack) hoặc nội bộ

[DATA MODEL CHANGE REQUEST] <mô tả thay đổi>

Bước 2: Cung cấp thông tin:

Bước 3: Core Team review:

Bước 4: Nếu được duyệt:

4.5. Ví dụ minh họa


Trường hợp hợp lệ

Partner thêm field vào entity module:

@Column(name = "PRIORITY")

private Integer priority;


Và có migration:

ALTER TABLE PLANNING ADD PRIORITY INT;


→ Được phép.


Trường hợp không hợp lệ

Partner sửa:

@Column(name = "CREATED_AT")

private String createdAt;


→ Đổi kiểu dữ liệu từ timestamp sang string.

→ Không được phép.

Phải tạo:

[DATA MODEL CHANGE REQUEST]



Trường hợp cần xin phép

Partner muốn:

4.6. Checklist áp dụng


Trước khi commit hoặc tạo PR:

Chương 2: Hướng dẫn phát triển (Developer)

5.Git Workflow & Pull Request

5.1. Mục tiêu chương


Chương này quy định chuẩn commit và quy trình merge code nhằm:

5.2. Khái niệm / phạm vi áp dụng


Quy định này áp dụng cho:

Quy trình phát triển tuân theo:

Điều kiện merge bắt buộc

5.3. Quy định chính

5.3.1. Format commit

Tất cả commit phải theo format:

<type>: <description>


Trong đó:

Ví dụ commit hợp lệ

feat: add planning API

fix: validate file upload

refactor: optimize planning service

docs: update API guideline

test: add unit test for planning service

chore: update dependency versions


Type hợp lệ

Type

Ý nghĩa

feat

Thêm chức năng mới

fix

Sửa lỗi

refactor

Tối ưu, không đổi logic

docs

Cập nhật tài liệu

test

Thêm hoặc sửa test

chore

Thay đổi cấu hình, build, dependency


5.3.2. Branch strategy

Hệ thống sử dụng mô hình branch chuẩn:

Branch

Mục đích

main

Bản stable, dùng cho production

develop

Bản tích hợp, nơi merge các feature

feature/*

Phát triển chức năng mới

fix/*

Sửa lỗi


Quy tắc làm việc với branch


Ví dụ tên branch hợp lệ

feature/planning-create-api

feature/document-upload

fix/login-null-pointer

fix/planning-date-validation


5.3.3. Điều kiện merge Pull Request

Pull Request chỉ được merge khi đáp ứng đầy đủ các điều kiện:

  1. Build thành công

Không sửa:

common/**

config/**

  1. Không sửa shared entity

Có review từ Core Team

5.4. Cách thực hiện / quy trình


Quy trình phát triển tính năng

Bước 1: Tạo branch từ develop

git checkout develop

git pull

git checkout -b feature/planning-create-api


Bước 2: Thực hiện commit theo chuẩn

git commit -m "feat: add planning create API"


Bước 3: Push branch lên remote

git push origin feature/planning-create-api


Bước 4: Tạo Pull Request vào develop

Bước 5: Core Team review

Bước 6: Nếu đạt yêu cầu → merge vào develop


Quy trình sửa lỗi

Bước 1: Tạo branch fix

git checkout develop

git checkout -b fix/planning-null-error


Bước 2: Commit

git commit -m "fix: handle null planning name"


Bước 3: Tạo PR và chờ review

5.5. Ví dụ minh họa

Trường hợp commit sai

Sai vì:


Trường hợp commit đúng

fix: handle null planning name


Trường hợp merge sai

Partner:

→ Vi phạm quy trình.


Trường hợp merge đúng

  1. Tạo branch feature/planning-api

  2. Commit theo chuẩn

  3. Tạo PR vào develop

  4. Core Team review

  5. Build pass

  6. Merge

5.6. Checklist áp dụng


Trước khi tạo Pull Request:

Chương 2: Hướng dẫn phát triển (Developer)

6.Checklist kiểm tra trước khi merge

6.1. Mục tiêu chương


Chương này quy định checklist bắt buộc trước khi merge code nhằm:

6.2. Khái niệm / phạm vi áp dụng


Checklist này áp dụng cho:

Checklist được dùng trong:

Kiểm tra trước khi release

6.3. Quy định chính


Pull Request chỉ được phép merge khi đáp ứng đầy đủ các điều kiện sau:

Code nằm trong khu vực được phép:

modules/**

  1. Không sửa các khu vực bị cấm:


common/**

config/**

  1. Không sửa shared entity trong:


common/entity/**

  1. Nếu có thay đổi database:

  2. API phải đúng chuẩn:

/api/v1/<module-name>


  1. Response phải dùng:

ApiResponse<T>


6.4. Cách thực hiện / quy trình

Quy trình kiểm tra trước khi merge

Bước 1: Dev hoàn thành feature hoặc fix bug
Bước 2: Tạo Pull Request vào develop
Bước 3: Dev tự kiểm tra checklist
Bước 4: Core Team review theo checklist
Bước 5: Nếu đạt yêu cầu → merge

Nếu không đạt:

6.5. Ví dụ minh họa

Trường hợp hợp lệ

PR:

Chỉ sửa code trong:

modules/planning/**

→ Được merge.


Trường hợp không hợp lệ

PR:

Sửa file:

common/entity/UserEntity.java

→ Vi phạm quy định shared entity.

→ PR bị từ chối.


Trường hợp bị từ chối do thiếu migration

PR:

Thêm field trong entity:

private Integer priority;

→ PR bị reject.

6.6. Checklist áp dụng


Dev phải tick đủ các mục sau trước khi merge:

[ ] Code nằm trong modules/**

[ ] Không sửa common/**

[ ] Không sửa config/**

[ ] Không sửa shared entity

[ ] Có migration nếu thay đổi DB

[ ] API đúng chuẩn /api/v1/

[ ] Response dùng ApiResponse

[ ] Build thành công


Core Team chỉ merge khi checklist đạt đầy đủ.

Chương 2: Hướng dẫn phát triển (Developer)

7.Quy trình phối hợp & quản lý thay đổi các nhóm

7.1. Mục tiêu chương


Chương này quy định cách phối hợp giữa nhiều đơn vị phát triển nhằm:

7.2. Khái niệm / phạm vi áp dụng


Áp dụng khi:

Mọi thay đổi liên quan:

đều phải thông qua Core Team.

7.3. Quy định chính

Nguyên tắc chung


Trường hợp FE và BE khác source, dùng chung DB

Rủi ro:

Quy định:

Quy trình:

  1. FE tạo ticket: API CONTRACT ISSUE

  2. BE/Core xác nhận

  3. Fix backward compatible hoặc tạo API v2


Trường hợp FE và BE dùng DB riêng khi dev

Quy định:

Quy trình:

  1. Nếu staging lỗi → so version migration

  2. Không cho merge nếu thiếu migration


Trường hợp nhiều FE dùng chung một BE

Quy định:

Quy trình:

  1. FE đăng ký client_id

  2. Core cấu hình redirectUri, scope

  3. Core cấp quyền


Trường hợp FE chung source, BE nhiều service

Quy định:

Quy trình:

  1. Thêm service → tạo Integration Checklist

  2. Core cấu hình CORS và token audience


Trường hợp thay đổi response làm FE vỡ

Quy định:

Quy trình:

  1. Tạo BREAKING CHANGE REQUEST

  2. Core duyệt

  3. Thông báo FE


Trường hợp xung đột entity/model

Quy định:

Quy trình:

  1. Partner đề xuất field + migration

  2. Core review

  3. Approve hoặc yêu cầu sửa


Trường hợp migration xung đột

Quy định:

Quy trình:

  1. Rebase

  2. Tạo migration mới

  3. Test staging


Trường hợp seed/master data bị sửa

Quy định:

Quy trình:

  1. Tạo MASTER DATA REQUEST

  2. Core duyệt và triển khai

7.4. Quy trình triển khai tính năng nhiều tầng


  1. Tạo ticket Feature

  2. Chốt API contract (OpenAPI)

  3. Chốt entity/migration

  4. BE implement

  5. FE integrate

  6. Test staging

  7. Release

7.5.Ví dụ minh họa


1. bảng mô tả các môi trường hệ thống:

Env FE URL BE URL DB Auth
dev http://localhost:4200 http://localhost:8080 local mock
staging https://staging.app.vn https://staging.api.vn shared WSO2 thật
prod https://app.vn https://api.vn prod WSO2 thật
Chương 2: Hướng dẫn phát triển (Developer)

8. Prompt Template cho AI Agent tạo API trên LifeESB

8.1. Vai trò và Nhiệm vụ (System Prompt)

Copy đoạn văn bản sau đưa cho AI:

Vai trò: Bạn là một Chuyên gia Kiến trúc Tích hợp (Integration Architect) hệ thống WSO2 Micro Integrator (MI) 4.4.0. Nhiệm vụ của bạn là đọc các bản đặc tả API (Use Cases) từ người dùng và tự động sinh ra mã nguồn XML chuẩn xác, có thể chạy được ngay của WSO2 MI.

Tiêu chuẩn Thiết kế (Bắt buộc tuân thủ 100%):

  1. Kiến trúc REST: Mọi API phải được bọc trong thẻ <api context="/... " name="...">. Các Use Case bên trong được chia thành các thẻ <resource methods="..." uri-template="...">.
  2. Phân tách Endpoint: Cấm hardcode URL trực tiếp vào <call>. Mọi URL gọi xuống backend phải được định dạng trong một file <endpoint> độc lập.
  3. Cấu hình Môi trường: Trong file <endpoint>, thuộc tính uri-template của <http> BẮT BUỘC phải dùng định dạng System Properties: $SYSTEM:TEN_BIEN_CUA_DICH_VU.
  4. Xử lý Dữ liệu mặc định là XML: Payload đi qua hệ thống mặc định là XML. Trừ khi được yêu cầu đổi sang JSON, còn lại không tự ý dùng DataMapper hay <property name="messageType" value="application/json"/>.
  5. Trích xuất thông tin: Trong inSequence của API, phải có các property sử dụng XPath để lấy các trường quan trọng từ payload XML đầu vào (như requestIdprocedureCoderegistrationId...).
  6. Ghi Log (Observability): Ngay sau khi vào inSequence và trích xuất property, phải có thẻ <log level="custom"> để in ra Console thông tin request (bao gồm Use Case ID đang chạy và các biến vừa lấy).
  7. Xử lý Ngoại lệ: Mọi <resource> bắt buộc phải có <faultSequence> để bẫy lỗi và trả response.
  8. Đầu ra (Output Mode): Chỉ in ra kết quả là các khối mã XML rõ ràng kèm tên file (comment ở dòng đầu), không giải thích dài dòng trừ khi người dùng hỏi.

8.2. Kiến thức Nền tảng (Few-Shot Examples)

Cung cấp cho AI đoạn mã mẫu sau làm chuẩn mực (Golden Template):

Dưới đây là Mẫu Code Tiêu Chuẩn bạn phải học theo:

Mẫu 1: File API định tuyến (ReceivingAPI.xml)

 

<?xml version="1.0" encoding="UTF-8"?>
<api context="/api/v1/receiving/registrations" name="ReceivingAPI" xmlns="http://ws.apache.org/ns/synapse">
    <resource methods="POST" uri-template="/">
        <inSequence>
            <log level="custom">
                <property name="Status" value="[UC-22] Received New Registration Request"/>
                <property name="requestId" expression="//requestId/text()"/>
                <property name="procedureCode" expression="//procedureCode/text()"/>
           </log>
            <call>
                <endpoint key="BackendReceivingCreateEndpoint"/>
            </call>
            <respond/>
        </inSequence>
        <outSequence/>
        <faultSequence>
            <log level="custom"><property name="Error" value="[UC-22] Failed"/></log>
            <respond/>
        </faultSequence>
    </resource>
</api>

Mẫu 2: File Endpoint độc lập (BackendReceivingCreateEndpoint.xml)

 

<?xml version="1.0" encoding="UTF-8"?>
<endpoint name="BackendReceivingCreateEndpoint" xmlns="http://ws.apache.org/ns/synapse">
    <http method="post" uri-template="$SYSTEM:URL_BACKEND_RECEIVING_CREATE">
        <suspendOnFailure>
            <initialDuration>2000</initialDuration>
            <progressionFactor>1.0</progressionFactor>
            <maximumDuration>3000</maximumDuration>
        </suspendOnFailure>
    </http>
</endpoint>

8.3. Cách thức làm việc (User Workflow)

Sau khi lưu 2 đoạn trên làm cấu hình cho Bot (System Instructions), bạn chỉ cần làm việc với nó qua các bước đơn giản mỗi lệnh:

Ví dụ một đoạn Chat bạn nhập vào:

Prompt: "Đây là tài liệu Use Case UC-26: [dán phần text tài liệu UC-26 Định tuyến thông điệp vào đây]. Yêu cầu: Generates file RoutingAPI.xml với context '/api/v1/routing' và các file Endpoints tương ứng. Chú ý sử dụng Switch mediator để rẽ nhánh procedureCode."

Kết quả hoàn hảo bot trả về: Bot sẽ lập tức sinh ra 1 file API có Switch mediator dùng XPath //procedureCode/text() cực chuẩn xác và 2-3 file cấu hình Endpoint $SYSTEM: cho nó.

Chương 2: Hướng dẫn phát triển (Developer)

9. Prompt Template cho AI Agent tạo Test Case API LifeESB

Bản Prompt này được tối ưu để hướng dẫn AI sinh ra các kịch bản kiểm thử (Test Cases), cURL commands, và Postman Collection dựa trên tài liệu đặc tả API và file mã nguồn WSO2 MI.

9.1. Vai trò và Nhiệm vụ (System Prompt)

Copy đoạn văn bản sau đưa cho AI:

Vai trò: Bạn là một QA / API Testing Engineer cấp cao chuyên về nền tảng tích hợp WSO2 Micro Integrator (MI). Nhiệm vụ của bạn là đọc các bản đặc tả Use Case và đoạn mã XML API của WSO2, từ đó sinh ra bộ Test Case hoàn chỉnh để kiểm thử API hoạt động đúng nghiệp vụ.

Tiêu chuẩn Thiết kế Test Case (Bắt buộc tuân thủ 100%):

  1. Dữ liệu Mặc định là XML: Mặc dù đặc tả có thể mô tả bằng JSON, nhưng Client giao tiếp với MI qua định dạng XML. Tất cả các Test Case payload (Body) BẮT BUỘC phải viết dưới dạng chuỗi XML hợp lệ.
  2. Cấu trúc Test Case: Mỗi Use Case phải có ít nhất 2 nhóm Test Case gốc:
    • Happy Path (Thành công): Dữ liệu chuẩn, mô phỏng luồng đi trót lọt (HTTP 200/202).
    • Negative Path (Báo lỗi): Dữ liệu cố tình làm sai (VD: thiếu dòng/sai XML, logic sai như thiếu chữ ký...) để kiểm thử khối <faultSequence> hoặc logic Validate của backend.
  3. Cú pháp Đầu ra (Output Format): Mỗi Test Case phải trình bày rõ ràng 4 phần:
    • Tên Test Case: (VD: TC01 - Tiếp nhận đăng ký thành công)
    • Endpoint & Method: (VD: POST http://localhost:8290/api/v1/receiving/registrations)
    • Headers: Định nghĩa rõ Content-Type: application/xml (hoặc application/json nếu có yêu cầu dị biệt).
    • Request Body (XML): Cấu trúc XML chuẩn xác theo nghiệp vụ.
  4. Postman/cURL Script: Cung cấp câu lệnh cURL tương ứng cho nhóm Happy Path để người dùng dễ dàng dán vào Terminal hoặc Import vào Postman test nhanh.
  5. Tập trung vào Giao diện (Integration Edge): Bạn chỉ test lớp Integration (Gateway/MI). Hãy chú ý truyền đúng các Param/Path Variable/Body mà WSO2 inSequence đang cần dùng (như requestIdprocedureCode).

9.2. Kiến thức Nền tảng (Few-Shot Examples)

Cung cấp cho AI ví dụ Test Case chuẩn mực sau:

Dưới đây là Mẫu Test Case Tiêu Chuẩn bạn phải học theo:

Use Case Mẫu: UC-22 Tiếp nhận hồ sơ mới (Nhận XML Ingestion)

TC01: Happy Path - Đăng ký hồ sơ thành công (Dữ liệu XML chuẩn mực)


<request>
    <requestId>REQ_20250228001</requestId>
    <procedureCode>HQ01</procedureCode>
    <enterpriseId>DN001</enterpriseId>
    <documents>
        <document>
            <documentType>INV</documentType>
            <content>Base-64-Encoded-Content</content>
        </document>
    </documents>
    <signature>
        <signedData>...</signedData>
    </signature>
</request>

cURL để Test nhanh TC01:

bash
curl -X POST -H 'Content-Type: application/xml' -H 'Accept: application/xml' -d '<request><requestId>REQ_20250228001</requestId><procedureCode>HQ01</procedureCode><enterpriseId>DN001</enterpriseId><documents><document><documentType>INV</documentType><content>Base-64-Encoded-Content</content></document></documents><signature><signedData>...</signedData></signature></request>' http://localhost:8290/api/v1/receiving/registrations

TC02: Negative Path - Payload XML bị khuyết cấu trúc nghiêm trọng (Lỗi Missing Mandatory Field)

<request>
    <requestId>REQ_20250228_ERR02</requestId>
    <!-- Cố tình thiếu procedureCode và enterpriseId -->
</request>

9.3. Cách thức làm việc (User Workflow)

Sau khi nhập 2 khối trên vào System Context, quy trình bạn ra lệnh sẽ như sau:

Ví dụ một đoạn Chat bạn nhập vào:

Prompt: "Tôi vừa viết xong WSO2 API cho UC-23 (Sửa hồ sơ /{registrationId}/amend). Đây là nguyên văn tài liệu: [Dán tài liệu UC-23]. Yêu cầu sinh Test Case cho API này theo định dạng XML. Context API gốc chày trên máy tôi là port 8290."

Kết quả hoàn hảo bot trả về: Bot sẽ sinh ra các bộ câu hỏi kiểm thử: HTTP Body (XML) sửa hồ sơ, Path params id kèm cURL Script. Bạn chỉ việc Copy ném vào Postman là bắn test luôn không phải typing mỏi tay định dạng từng dấu ngoặc.

Chương 3: Triển khai hệ thống (Deploy)

Chương này hướng dẫn quy trình build và triển khai ứng dụng Java (Spring Boot) lên các môi trường dev/staging/production. Bao gồm chuẩn bị hạ tầng, build artifact (JAR), cấu hình biến môi trường, chạy ứng dụng dạng service hoặc Docker container, cấu hình reverse proxy (Nginx), database migration và kiểm tra sau triển khai. Mục tiêu đảm bảo ứng dụng chạy ổn định, có thể giám sát và rollback khi cần.

Chương 3: Triển khai hệ thống (Deploy)

Hướng dẫn chi tiết

1. Yêu cầu hạ tầng

Server tối thiểu

Cài Java:


sudo apt update sudo apt install openjdk-17-jdk -y java -version

2. Build ứng dụng

Build bằng Maven

mvn clean package -DskipTests

Artifact tạo ra:

target/app.jar

 

Chương 4: Kiểm thử đơn vị (Unit Test)

Chương này hướng dẫn cách viết unit test cho API trong ứng dụng Java Spring Boot. Nội dung bao gồm cấu trúc test chuẩn, cách mock dependency, kiểm tra validate input, xác thực (authentication/authorization), ghi log, xử lý exception và kiểm tra logic service. Mục tiêu đảm bảo mỗi API hoạt động đúng, an toàn và ổn định trước khi tích hợp hoặc triển khai.

Chương 4: Kiểm thử đơn vị (Unit Test)

Nội dung chi tiết

 

1. Nguyên tắc Unit Test API

Mỗi API cần test các nhóm case cơ bản:

  1. Validate input

  2. Auth / permission

  3. Business logic

  4. Repository interaction

  5. Exception handling

  6. Logging

  7. HTTP status & response


2. Ví dụ API mẫu

Controller:


@RestController @RequestMapping("/users") @RequiredArgsConstructor public class UserController { private final UserService userService; @PostMapping public ResponseEntity<UserResponse> create( @Valid @RequestBody CreateUserRequest req) { return ResponseEntity.ok(userService.create(req)); } }

DTO:


@Data public class CreateUserRequest { @NotBlank private String username; @Email private String email; }

Service:


@Service @RequiredArgsConstructor public class UserService { private final UserRepository repo; public UserResponse create(CreateUserRequest req) { User u = new User(req.getUsername(), req.getEmail()); repo.save(u); return new UserResponse(u.getId(), u.getUsername()); } }

3. Cấu trúc test chuẩn

Dependency:


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>

Controller test dùng:


4. Test Validate Input

Case cần có:


@WebMvcTest(UserController.class) class UserControllerValidationTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void create_shouldFail_whenUsernameBlank() throws Exception { String json = """ {"username":"","email":"a@test.com"} """; mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isBadRequest()); } @Test void create_shouldFail_whenEmailInvalid() throws Exception { String json = """ {"username":"john","email":"invalid"} """; mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isBadRequest()); } }

5. Test Auth / Permission

Giả sử API cần login:


@WithMockUser @Test void create_shouldSuccess_whenAuthenticated() throws Exception { when(userService.create(any())) .thenReturn(new UserResponse(1L,"john")); String json = """ {"username":"john","email":"a@test.com"} """; mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()); }

Test chưa login:


@Test void create_shouldUnauthorized_whenNoAuth() throws Exception { String json = """ {"username":"john","email":"a@test.com"} """; mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isUnauthorized()); }

6. Test Business Logic (Service)


@ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository repo; @InjectMocks private UserService service; @Test void create_shouldSaveUser() { CreateUserRequest req = new CreateUserRequest(); req.setUsername("john"); req.setEmail("a@test.com"); when(repo.save(any())).thenAnswer(i -> i.getArgument(0)); UserResponse res = service.create(req); assertEquals("john", res.getUsername()); verify(repo).save(any()); } }

7. Test Exception Handling

Giả sử email trùng:


when(repo.save(any())) .thenThrow(new DataIntegrityViolationException("dup"));

Test:


@Test void create_shouldThrow_whenDuplicateEmail() { CreateUserRequest req = new CreateUserRequest(); req.setUsername("john"); req.setEmail("a@test.com"); when(repo.save(any())) .thenThrow(new RuntimeException("duplicate")); assertThrows(RuntimeException.class, () -> service.create(req)); }

8. Test Logging

Giả sử service có log:


log.info("Create user {}", req.getUsername());

Test log:


@ExtendWith(MockitoExtension.class) class UserServiceLogTest { @Mock private UserRepository repo; @InjectMocks private UserService service; @Test void create_shouldLog() { CreateUserRequest req = new CreateUserRequest(); req.setUsername("john"); req.setEmail("a@test.com"); when(repo.save(any())).thenAnswer(i -> i.getArgument(0)); service.create(req); verify(repo).save(any()); } }

(Thực tế có thể dùng LogCaptor)


9. Test HTTP Response


@Test @WithMockUser void create_shouldReturnBody() throws Exception { when(userService.create(any())) .thenReturn(new UserResponse(1L,"john")); String json = """ {"username":"john","email":"a@test.com"} """; mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) .andExpect(jsonPath("$.username").value("john")); }

10. Danh sách Case chuẩn cho mỗi API

Mỗi API cần tối thiểu:

Validate

Auth

Business

Repository

Exception

Response

Log

 


11. Checklist Unit Test API

Chương 5: Đóng gói & phát hành (Packaging)

Chương này hướng dẫn cách đóng gói dự án thành artifact phát hành (jar, docker image, bundle web…), đặt version, ghi changelog, ký số (nếu có) và lưu trữ trên repository. Kết quả là gói cài đặt sẵn sàng để triển khai hoặc phân phối.