Vấn Đề Đặt Ra
Nếu bạn đã từng làm việc với một nền tảng e-commerce cần kết nối với SAP, bạn sẽ hiểu ngay cái "vui vẻ" đó: hai thế giới hoàn toàn khác nhau về cách nhìn nhận dữ liệu, thời gian xử lý, và định nghĩa của một "lỗi." Một bên là ứng dụng web hiện đại cần phản hồi dưới một giây trong quá trình thanh toán. Bên kia là hệ thống ERP được thiết kế cho xử lý batch và quy trình back-office, giao tiếp qua SOAP.
Bài viết này trình bày kiến trúc chúng tôi đã xây dựng để kết nối hai thế giới đó — luồng dữ liệu từ storefront đến SAP, cách xử lý lỗi, và các quyết định thiết kế chúng tôi sẽ làm lại hoặc làm khác đi.
Tổng Quan Kiến Trúc: 3 Tầng
┌──────────────────────────────────────────────┐
│ Web Layer (Controllers / API Endpoints) │
│ Xử lý HTTP request, auth, routing │
├──────────────────────────────────────────────┤
│ Application Layer (Domain Services) │
│ Điều phối business logic, mapping models │
├──────────────────────────────────────────────┤
│ ERP Integration Layer (SOAP Clients) │
│ Chuyển đổi domain objects → SOAP calls │
│ Quản lý credentials, timeout, retry │
└──────────────────────────────────────────────┘
Điểm mấu chốt: tầng tích hợp là project riêng biệt, độc lập. Khi SAP thay đổi WSDL (và chắc chắn sẽ thay đổi), phạm vi ảnh hưởng chỉ nằm trong một project duy nhất.
Luồng Dữ Liệu: Từ Giỏ Hàng Đến SAP
Quy Trình 2 Giai Đoạn
Giai đoạn 1 — Mô phỏng đơn hàng (trong lúc checkout)
[Giỏ hàng] → [YourOrderService].SimulateOrder()
→ Chuyển đổi address DTO → [ErpAddress]
→ Chuyển đổi line items → [ErpLineItem[]]
→ Load cấu hình ERP theo locale
→ Gọi ERP SOAP endpoint: SimulateOrder
→ Parse response → lấy tổng thuế + phí vận chuyển
→ Trả về giao diện checkout
Giai đoạn 2 — Tạo đơn hàng (sau khi thanh toán)
[OrderPlaced Event] → [YourOrderHandler].Handle()
→ Load cấu hình ERP theo locale
→ Kiểm tra real-time ordering có được bật không
→ Tạo địa chỉ giao hàng + thanh toán
→ Gọi ERP SOAP endpoint: CreateOrder
→ Thành công: lưu mã đơn hàng ERP
→ Thất bại: cập nhật trạng thái, thông báo admin
Tách Biệt Bằng Event-Driven
// Minh họa — không phải code production thực tế
public class [YourOrderHandler] : INotificationHandler<[OrderPlacedEvent]>
{
public async Task Handle([OrderPlacedEvent] notification, CancellationToken ct)
{
var erpConfig = _configProvider.GetErpConfig(currentLocale);
if (erpConfig == null || !erpConfig.RealTimeOrderEnabled)
{
await _mediator.Publish(new [StaffNotification](order), ct);
return;
}
var errors = _erpOrderService.CreateOrder(
shippingAddress, billingAddress, lineItems,
paymentInfo, erpConfig, out string erpOrderNumber);
if (errors.Any())
await HandleErpFailure(order, errors, ct);
else
await HandleErpSuccess(order, erpOrderNumber, ct);
}
}
Sự tách biệt này rất quan trọng: luồng checkout không bị chặn bởi SAP. Nếu SAP bị down, thanh toán của khách hàng không bị ảnh hưởng.
SOAP Client: Cấu Hình Quan Trọng Hơn Bạn Nghĩ
private [ErpSoapClient] BuildSoapClient()
{
var binding = new BasicHttpBinding
{
Security = {
Mode = BasicHttpSecurityMode.Transport,
Transport = { ClientCredentialType = HttpClientCredentialType.Basic }
},
MaxReceivedMessageSize = 20_000_000,
SendTimeout = TimeSpan.FromSeconds(90)
};
var client = new [ErpSoapClient](binding, new EndpointAddress(_erpEndpoint));
client.ClientCredentials.UserName.UserName = _erpUsername;
client.ClientCredentials.UserName.Password = _erpPassword;
return client;
}
- Timeout 90 giây. SAP không nhanh. Timeout mặc định của WCF sẽ làm hỏng order creation.
- Buffer 20MB. Response lịch sử đơn hàng B2B có thể rất lớn.
- Không tái sử dụng client. WCF channel faulting khiến tất cả các lần gọi sau thất bại nếu dùng cùng instance.
Xử Lý Lỗi: Phân Loại Các Kiểu Lỗi SAP
1. Validation Trước Khi Gọi
if (config.SalesOrganization.IsNullOrEmpty())
{
errors.Add(new [ErpMessage]("E", "", "Thiếu cấu hình ERP cho locale này"));
return errors;
}
2. Lỗi Từ SOAP Response
var fatalErrors = erpResponse.Messages
.Where(m => m.Type == "E" || m.Type == "Error")
.Where(m => !(m.Number == "219" && m.Category == "V4")) // Non-fatal đã biết
.Select(m => new [ErpMessage](m.Type, m.Number, m.Text));
3. Null Response & 4. Exception
Mọi tầng của response phải được null-check phòng thủ. Network timeout và SOAP fault được bắt ở tầng cao nhất và kích hoạt pipeline thông báo admin.
Pipeline Thông Báo Admin
Đồng bộ thất bại
│
├── Cập nhật trạng thái → AwaitingExchange
├── Gắn ghi chú lỗi vào đơn hàng
└── Gửi email đến ERP admin
└── Fallback: locale hiện tại → locale cha → Tiếng Anh
Đồng Bộ Khách Hàng: Nửa Còn Lại
E-commerce → SAP: Tạo khách hàng, xác minh địa chỉ, kiểm tra trùng VAT/PAN
SAP → E-commerce: Đồng bộ địa chỉ (SAP là master), gán mã khách hàng
SAP → Identity: Mã khách hàng SAP đẩy ngược về identity provider
Bài Học Rút Ra
- Cấu hình SAP theo từng locale, không phải global. Phải phản ánh điều này từ đầu.
- "Simulate trước khi tạo" cứu đơn hàng. Phát hiện vấn đề giá/thuế trước khi thu tiền.
- Không phải "error" SAP nào cũng là lỗi thật. Xây dựng catalog các non-fatal message.
- SOAP chưa chết, nó chỉ đang ẩn náu. Thiết kế transport như một layer có thể thay thế.
- Mapping payment phức tạp hơn bạn nghĩ. Khác nhau theo processor và thị trường.
- Đừng tin giá trị âm. Validate tổng thuế/phí vận chuyển từ SAP trước khi đưa vào checkout.
Kết Luận
Xây dựng tích hợp e-commerce với SAP về bản chất là bài toán dịch thuật. Cô lập tầng dịch thuật, validate chặt chẽ ở mọi ranh giới, và luôn giả định mọi lần gọi SAP đều có thể thất bại theo những cách bạn chưa nghĩ đến.
Bài viết mô tả các pattern kiến trúc từ các tích hợp SAP e-commerce thực tế. Tất cả các định danh và chi tiết triển khai đã được ẩn danh hóa.