diff --git a/en/docs/guides/guides.md b/en/docs/guides/guides.md index 032a0901309..472d21de4a8 100644 --- a/en/docs/guides/guides.md +++ b/en/docs/guides/guides.md @@ -16,12 +16,14 @@ Complete, end-to-end examples you can follow from start to finish. ## Enterprise integration patterns -Reusable integration patterns you can apply across projects: - -- **[Message](patterns/message.md)** -- Package business data and metadata for transmission through a message channel -- **[Message Filter](patterns/message-filter.md)** -- Route only messages that match a condition -- **[Content Based Routing](patterns/content-based-routing.md)** -- Route messages by inspecting their content -- **[Channel Adapter](patterns/channel-adapter.md)** -- Connect applications, services, or broker channels to an integration flow +Reusable integration patterns you can apply across projects. Start with the [Enterprise Integration Patterns overview](patterns/overview.md), or jump to a category: + +- **Messaging Systems** -- The basic building blocks: [Message](patterns/message.md), [Pipes and Filters](patterns/pipes-and-filters.md), [Message Router](patterns/message-router.md), [Message Translator](patterns/message-translator.md), [Message Endpoint](patterns/message-endpoint.md) +- **Messaging Channels** -- How messages move between applications: [Point-to-Point Channel](patterns/point-to-point-channel.md), [Channel Adapter](patterns/channel-adapter.md), [Messaging Bridge](patterns/messaging-bridge.md) +- **Message Construction** -- The intent and form of messages: [Command Message](patterns/command-message.md), [Document Message](patterns/document-message.md), [Event Message](patterns/event-message.md), [Message Sequence](patterns/message-sequence.md), [Format Indicator](patterns/format-indicator.md) +- **Message Routing** -- Getting messages to the right receiver: [Content-Based Router](patterns/content-based-router.md), [Message Filter](patterns/message-filter.md), [Splitter](patterns/splitter.md), [Aggregator](patterns/aggregator.md), [Routing Slip](patterns/routing-slip.md), [Process Manager](patterns/process-manager.md) +- **Message Transformation** -- Changing message content: [Content Enricher](patterns/content-enricher.md), [Content Filter](patterns/content-filter.md), [Normalizer](patterns/normalizer.md) +- **Messaging Endpoints and System Management** -- Consuming and managing messages: [Idempotent Receiver](patterns/idempotent-receiver.md), [Message Store](patterns/message-store.md) ## Migration guides diff --git a/en/docs/guides/patterns/aggregator.md b/en/docs/guides/patterns/aggregator.md new file mode 100644 index 00000000000..f948dd62ffb --- /dev/null +++ b/en/docs/guides/patterns/aggregator.md @@ -0,0 +1,72 @@ +--- +title: Aggregator +description: "Implement the Aggregator pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Aggregator + +Use an Aggregator to collect individual but related messages and publish a single combined message once the set is complete. + +In WSO2 Integrator, the aggregator keeps the partial messages in a map keyed by a correlation identifier, appends each arriving message to its group, and sends the combined message when the completeness condition is met. + +## Example: Combining multi-part survey responses + +This example collects survey form submissions that arrive one section at a time. Each submission is correlated by the `userId` header and stored with the user's earlier sections. When all three sections have arrived, the aggregator submits the complete survey to the survey API and clears the stored parts. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the survey section payload and the `userId` correlation header. +2. Add an HTTP client connection for the survey submission API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, look up the user's partial submissions in the aggregation map by `userId`. +4. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if): when no entry exists, store the first section; otherwise append the new section. +5. When the stored sections reach the completeness condition (three sections), post the combined survey to the submission endpoint and remove the entry from the map. + +In the flow, the **If** node checks whether this is the user's first survey section; once three sections have accumulated, the combined survey is posted and the stored parts are cleared: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +final http:Client formSubmitClient = check new ("http://api.surveyme.com.balmock.io"); +// docs-fold-end + +map partialSurveys = {}; + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post survey/[string id](@http:Header string userId, @http:Payload json formData) returns error? { + json[]|() surveyData = partialSurveys[userId]; + if surveyData == () { + json[] newSurvey = [formData]; + partialSurveys[userId] = newSurvey; + } else { + () var1 = surveyData.push(formData); + if surveyData.length() == 3 { + http:Response response = check formSubmitClient->/survey/[id]/submit.post({userId: surveyData}, targetType = http:Response); + json[] remove = partialSurveys.remove(userId); + } + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [aggregator sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/aggregator) on GitHub. diff --git a/en/docs/guides/patterns/channel-adapter.md b/en/docs/guides/patterns/channel-adapter.md index ccf95466867..ffc363ec86d 100644 --- a/en/docs/guides/patterns/channel-adapter.md +++ b/en/docs/guides/patterns/channel-adapter.md @@ -6,145 +6,61 @@ description: "Implement the Channel Adapter pattern with WSO2 Integrator." import TabItem from '@theme/TabItem'; import { EipReferenceLink, + PatternImage, PatternImplementationTabs, } from '@site/src/utils/eipPatternComponents'; # Channel Adapter -Use a Channel Adapter to define the boundary between an integration flow and the transport, application, or broker channel that supplies or receives messages. +Use a Channel Adapter to connect an application that was not built for messaging to the messaging system, so the integration can send messages to it and receive messages from it through the application's own API. -The adapter is usually implemented at the edge of the flow. Use connector clients for application APIs, services and listeners for inbound endpoints, and broker connectors only when the channel itself is Kafka, RabbitMQ, or JMS. Keep payloads typed, and keep endpoints and credentials in configurables. +In WSO2 Integrator, connectors are channel adapters. A connector client wraps an external application's API (its authentication, endpoints, and payload formats) and exposes it to the flow as typed operations, so the rest of the integration works with messages instead of API details. -## API/SaaS channel adapter with connector client +## Example: Adapting Jira into the integration -A connector client adapts an external application API into the integration flow. Create the connection with managed connection settings, call the required connector operation, and pass the typed result into the next step. +This example uses the Jira connector as a channel adapter. The connector handles authentication and the Jira REST API details, and the flow simply calls the `getProject` operation to receive a typed `jira:Project` message. -1. Add the connector client connection for the application channel. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection); for this example, select the Jira connector as shown in [adding the Jira connector](../../connectors/catalog/productivity-collaboration/jira/example.md#adding-the-jira-connector). -2. Configure the endpoint, authentication values, and other connection properties with project configurables. Use the connector-specific [Jira setup guide](../../connectors/catalog/productivity-collaboration/jira/setup-guide.md) and [Jira connection configuration steps](../../connectors/catalog/productivity-collaboration/jira/example.md#configuring-the-jira-connection). -3. Add the connector operation that reads from or writes to the application channel. Use the [Jira action reference](../../connectors/catalog/productivity-collaboration/jira/actions.md#projects) to select the project operation for this example. -4. Map the connector response to the message shape used by the rest of the flow. +The Jira connector is the channel adapter: it exposes the external Jira API to the automation as a typed connection: - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerinax/jira; - -configurable string username = ?; -configurable string password = ?; -configurable string serviceUrl = ?; - -jira:ConnectionConfig jiraConfig = { - auth: { - username, - password - } -}; - -jira:Client jiraAdapter = check new ( - jiraConfig, - serviceUrl = serviceUrl -); -// docs-fold-end + -public function readProject(string projectKey) returns jira:Project|error { - return jiraAdapter->/api/'3/project/[projectKey]; -} -``` - - - - -## HTTP service as inbound channel adapter +1. Create an [automation](../../develop/integration-artifacts/automation.md#creating-an-automation) to run the flow. +2. Add a Jira connection with the username and password kept as [configurable values](../../reference/config/configuration-management.md#configurable-variables). See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, call the `getProject` operation on the Jira connection to bring the project into the integration as a typed message. -An HTTP service adapts an inbound HTTP channel into an integration flow. Define the service resource as the adapter entry point, receive a typed request payload, and return the typed response expected by the caller. See [creating an HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) for the service setup flow. +The flow calls the `getProject` operation on the Jira connection; the connector handles the API details and returns a typed result: - - - -1. Create an HTTP service for the inbound channel. See [creating an HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service). -2. Add the resource that represents the inbound adapter operation. Use [resource inputs](../../develop/integration-artifacts/service/http.md#defining-inputs) to define the request payload or parameters. -3. Define the response payload type for the resource with [response schemas](../../develop/integration-artifacts/service/http.md#defining-response-schemas). -4. Add the flow logic that transforms or forwards the received message. + ```ballerina // docs-fold-start: Supporting definitions -import ballerina/http; import ballerinax/jira; -listener http:Listener projectListener = new (8080); +configurable string username = "admin"; +configurable string password = "admin"; -type ProjectRequest record {| - string projectKey; -|}; - -type ProjectResponse record {| - string key; - string name; -|}; +final jira:Client jiraAdapter = check new ({ + auth: { + username: username, + password: password + } +}, "http://wso2.jira.com.balmock.io"); // docs-fold-end -service /projects on projectListener { - resource function post lookup(ProjectRequest request) returns ProjectResponse|error { - jira:Project project = check readProject(request.projectKey); - return mapProject(project); - } +public function main() returns error? { + jira:Project result = check jiraAdapter->getProject("EI-Patterns-With-Ballerina"); } ``` -## Broker-backed channel adapter - -Use a broker listener when the channel is a messaging broker rather than an application API or HTTP endpoint. The service receives records from the broker, converts each record into the flow payload, and acknowledges or publishes through the connector according to the channel contract. Use the relevant broker guide, such as [Kafka consumers](../../develop/integration-artifacts/event/kafka.md#creating-a-kafka-listener), [RabbitMQ services](../../develop/integration-artifacts/event/rabbitmq.md#creating-a-rabbitmq-service), or the [JMS listener](../../connectors/catalog/messaging/java.jms/triggers.md#listener). - - - - -1. Create the broker listener for the channel. For Kafka, see [creating a Kafka consumer](../../develop/integration-artifacts/event/kafka.md#creating-a-kafka-listener); for RabbitMQ, see [creating a RabbitMQ service](../../develop/integration-artifacts/event/rabbitmq.md#creating-a-rabbitmq-service); for JMS, see the [JMS listener](../../connectors/catalog/messaging/java.jms/triggers.md#listener). -2. Configure the broker endpoint, topic or queue, and credentials with configurables. For Kafka, use [service configuration](../../develop/integration-artifacts/event/kafka.md#service-configuration); for RabbitMQ, use [listener configuration](../../develop/integration-artifacts/event/rabbitmq.md#listener-configuration). -3. Add the message-handling function for the broker event. For Kafka, use [service configuration](../../develop/integration-artifacts/event/kafka.md#service-configuration); for RabbitMQ, use [event handlers](../../develop/integration-artifacts/event/rabbitmq.md#event-handlers). -4. Convert the broker record to the typed payload used inside the flow. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerinax/kafka; - -configurable string brokerUrl = ?; - -type ProjectEvent record {| - string projectKey; - string source; -|}; - -listener kafka:Listener projectEventListener = new ({ - bootstrapServers: brokerUrl, - groupId: "project-adapter" -}); - -function handleProjectEvent(ProjectEvent event) returns error? { -} -// docs-fold-end - -service on projectEventListener { - remote function onConsumerRecord(ProjectEvent[] events) returns error? { - foreach ProjectEvent event in events { - check handleProjectEvent(event); - } - } -} -``` +## Complete sample - - +The complete project is available in the [channel adapter sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/channel_adapter) on GitHub. diff --git a/en/docs/guides/patterns/command-message.md b/en/docs/guides/patterns/command-message.md new file mode 100644 index 00000000000..3325fdcb1a4 --- /dev/null +++ b/en/docs/guides/patterns/command-message.md @@ -0,0 +1,90 @@ +--- +title: Command Message +description: "Implement the Command Message pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Command Message + +Use a Command Message to invoke a procedure in another application through messaging. The message carries the command and its parameters, and the receiver executes it. + +In WSO2 Integrator, a command message is a typed record sent to the operation endpoint of the receiving application. The record fields are the procedure's parameters, and the response confirms the command's outcome. + +## Example: Creating a Slack user group + +This example receives a user group creation request and sends it as a command message to the Slack `usergroups.create` API. The `UserGroupCreateRequest` record carries the command parameters, and Slack executes the procedure and returns the created group. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `UserGroupCreateRequest` payload. +2. Add an HTTP client connection for the Slack API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, post the command message to the `usergroups.create` operation with the `x-www-form-urlencoded` media type. +4. Return the `UserGroupCreationResponse` to the caller. + +The `UserGroupCreateRequest` record is the command message, and its fields (`name`, `description`, and `team_id`) are the parameters of the procedure to invoke: + + + +The flow forwards the command message to Slack's `usergroups.create` operation and returns the result: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type UserGroupCreateRequest record {| + string name; + string description; + string team_id; +|}; + +type UserGroup record { + string id; + boolean is_usergroup; + string 'handle; + boolean is_external; + int date_create; + string created_by; + string user_count; + string name; + string description; + string team_id; +}; + +type UserGroupCreationResponse record { + boolean ok; + UserGroup usergroup?; + string 'error?; +}; + +final http:Client slackClient = check new ("http://api.slack.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + isolated resource function post createUserGroup(UserGroupCreateRequest userGroup) + returns UserGroupCreationResponse|error { + UserGroupCreationResponse userGroupCreateRequest = check slackClient->/api/usergroups\.create.post(userGroup, mediaType = "x-www-form-urlencoded"); + return userGroupCreateRequest; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [command message sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/command_message) on GitHub. diff --git a/en/docs/guides/patterns/content-based-router.md b/en/docs/guides/patterns/content-based-router.md new file mode 100644 index 00000000000..991bf4dcbfa --- /dev/null +++ b/en/docs/guides/patterns/content-based-router.md @@ -0,0 +1,92 @@ +--- +title: Content-Based Router +description: "Implement the Content-Based Router pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Content-Based Router + +Use a Content-Based Router to examine each message and route it to the correct recipient based on the data the message contains, so one logical function can be served by multiple physical systems. + +In WSO2 Integrator, the router is implemented at the point where the flow has enough message content to choose the recipient: an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) for predicate-based routing or a [Match node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#match) for value-based routing. + +## Example: Routing tracking requests by country + +This example routes shipment tracking requests by the destination country in the request. UK shipments are routed to the DHL Parcel UK API, and other shipments are routed to the DHL Deutsche Post International API. The caller sees one tracking endpoint regardless of which physical system answers. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `get` resource that accepts the country and tracking number as path parameters. +2. Add an HTTP client connection for the DHL API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) on the routing field with the condition `country is UK`. +4. In the **True** branch, call the DHL Parcel UK tracking endpoint and return the shipment status. +5. In the **False** branch, call the DHL Deutsche Post International tracking endpoint and return the event status. + +The flow branches on the message content: UK shipments are routed to the DHL Parcel UK API and everything else to the DHL Deutsche Post International API: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type DhlUkResponse record {| + string url; + ShipmentData[] shipments; +|}; + +type ShipmentData record {| + string id; + Status status; +|}; + +type DhlDpiResponse record {| + Status[] events; + string publicUrl; + string barcode; +|}; + +type Status record {| + string statusCode; + string status; +|}; + +enum Country { + UK, + DE +} + +final http:Client dhl = check new ("http://api.dhl.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /shipments on httpListener { + resource function get [Country country]/[string trackingNumber]/status() returns string|error { + if country is UK { + DhlUkResponse response = check dhl->/parceluk/tracking/v1/shipments.get(trackingNumber = trackingNumber); + return response.shipments[0].status.status; + } else { + DhlDpiResponse response = check dhl->/dpi/tracking/v1/trackings/[trackingNumber].get(); + return response.events[0].status; + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [content-based router sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/content_based_router) on GitHub. diff --git a/en/docs/guides/patterns/content-based-routing.md b/en/docs/guides/patterns/content-based-routing.md deleted file mode 100644 index 46e7809acbe..00000000000 --- a/en/docs/guides/patterns/content-based-routing.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: Content Based Routing -description: "Implement the Content Based Routing pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Content Based Routing - -Use Content Based Routing to inspect each message and send it to the recipient that handles that message shape, value, or rule outcome. - -The pattern is implemented at the decision point where the flow has enough message content to choose the next recipient. Use a match-based route for stable routing keys, and use predicate-based routing when the route depends on ranges, optional fields, or multiple content checks. - -## Pattern-based content routing - -Use pattern-based content routing with [match expressions](../../develop/understand-ide/editors/flow-diagram-editor/control.md#match) when each recipient maps to a known field value, such as a message type, region, product category, or event name. Keep an explicit default branch so unsupported content is handled in a dedicated fallback path for invalid recipients. - - - - -1. Create or open the [HTTP service resource](../../develop/integration-artifacts/service/http.md#creating-an-http-service) that receives the routed message. -2. Add HTTP client connections for each recipient. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection) and the [HTTP client reference](../../connectors/catalog/built-in/http/action-reference.md#client). -3. Open the resource flow and [add a step](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#anatomy-of-the-editor). -4. Add a [Match node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#match) and set the expression to the routing field, such as `order.itemType`. -5. Add one branch for each accepted value, such as `"standard"` and `"express"`, and add `_` as the default branch. -6. In each accepted branch, add the connector call for that recipient and return the route result. -7. In the default branch, return an error response for unsupported content. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; - -configurable string standardFulfillmentUrl = ?; -configurable string expressFulfillmentUrl = ?; - -type OrderRequest record {| - string orderId; - string itemType; - int quantity; -|}; - -type RouteResponse record {| - string route; - json result; -|}; - -final http:Client standardFulfillment = check new (standardFulfillmentUrl); -final http:Client expressFulfillment = check new (expressFulfillmentUrl); -// docs-fold-end - -service /orders on new http:Listener(8080) { - resource function post route(OrderRequest order) - returns RouteResponse|http:BadRequest|error { - match order.itemType { - "standard" => { - json result = check standardFulfillment->post("/orders", order); - return {route: "standard-fulfillment", result}; - } - "express" => { - json result = check expressFulfillment->post("/orders", order); - return {route: "express-fulfillment", result}; - } - _ => { - return { - body: {message: "Unsupported item type"} - }; - } - } - } -} -``` - - - - -## Predicate-based content routing - -Use predicate-based content routing with [if/else statements](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) when the route depends on content rules instead of one stable routing key. - - - - -1. Create or open the resource or function that contains the routing decision. -2. Add HTTP client connections for the possible recipients. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection) and the [HTTP client reference](../../connectors/catalog/built-in/http/action-reference.md#client). -3. Add a [configurable variable](../../reference/config/configuration-management.md#configurable-variables) for any route rule that should change by environment, such as `bulkThreshold`. -4. Open the flow and add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) with a condition such as `order.quantity >= bulkThreshold`. -5. Add the bulk recipient call inside the **True** branch. -6. In the **False** branch, add another **If** node with a condition such as `order.priority`. -7. Add the priority recipient call inside the nested **True** branch and the default recipient call inside the nested **False** branch. -8. Return the selected route result from each branch. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; - -configurable int bulkThreshold = 100; -configurable string bulkFulfillmentUrl = ?; -configurable string priorityFulfillmentUrl = ?; -configurable string defaultFulfillmentUrl = ?; - -type OrderRequest record {| - string orderId; - string itemType; - int quantity; - boolean priority = false; -|}; - -type RouteResponse record {| - string route; - json result; -|}; - -final http:Client bulkFulfillment = check new (bulkFulfillmentUrl); -final http:Client priorityFulfillment = check new (priorityFulfillmentUrl); -final http:Client defaultFulfillment = check new (defaultFulfillmentUrl); -// docs-fold-end - -function routeByOrderRules(OrderRequest order) returns RouteResponse|error { - if order.quantity >= bulkThreshold { - json result = check bulkFulfillment->post("/orders", order); - return {route: "bulk-fulfillment", result}; - } else if order.priority { - json result = check priorityFulfillment->post("/orders", order); - return {route: "priority-fulfillment", result}; - } else { - json result = check defaultFulfillment->post("/orders", order); - return {route: "default-fulfillment", result}; - } -} -``` - - - diff --git a/en/docs/guides/patterns/content-enricher.md b/en/docs/guides/patterns/content-enricher.md new file mode 100644 index 00000000000..813ec69e9f5 --- /dev/null +++ b/en/docs/guides/patterns/content-enricher.md @@ -0,0 +1,97 @@ +--- +title: Content Enricher +description: "Implement the Content Enricher pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Content Enricher + +Use a Content Enricher when the message originator does not have all the data items the target system requires. The enricher uses information in the message to retrieve the missing data from an external source and appends it before forwarding the message. + +In WSO2 Integrator, the enricher is a connector call placed before the target call: it looks up the missing data, and a spread expression merges the original message with the enriched fields. + +## Example: Enriching bank account requests with a bank code + +This example creates customer bank accounts in QuickBooks, which requires a bank code that the original request does not carry. The enricher uses the account number and country from the request to look up the bank code from the IBAN service, merges it into the message, and forwards the enriched request. + + + + +The design view shows the two systems the enricher works with: the IBAN service it reads the missing bank code from, and the QuickBooks endpoint it sends the enriched message to: + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `BankAccountReq` payload. +2. Add HTTP client connections for the IBAN lookup service and the Intuit QuickBooks API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, build the `IbanRequest` from the country and account number in the message and call the IBAN service to retrieve the bank code. +4. Post the original request merged with the retrieved `bankCode` to the QuickBooks bank accounts endpoint, and return the created `BankAccount`. + +The flow first calls the IBAN service to look up the bank code, then sends the enriched request to QuickBooks: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type BankAccountReq record {| + string name; + string accountNumber; + string routingNumber; + string|() country; +|}; + +type IbanRequest record {| + "json"|"xml" format = "json"; + string country_iso; + string nid; +|}; + +type IbanResponse record { + string bank_code; +}; + +type BankAccount record { + string id; + string|() bankCode; + string name; + string accountNumber; + string routingNumber; + string|() country; +}; + +final http:Client iban = check new ("http://api.iban.com.balmock.io"); +final http:Client intuit = check new ("http://api.intuit.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /finance on httpListener { + resource function post customers/[int id]/accounts(BankAccountReq req) returns BankAccount|error { + IbanRequest ibanReq = {country_iso: req.country ?: "US", nid: req.accountNumber}; + IbanResponse ibanRes = check iban->/clients/api/banksuite/nid.post(ibanReq); + BankAccount result = check intuit->/quickbooks/v4/customers/[id]/bank\-accounts.post({...req, bankCode: ibanRes.bank_code}); + return result; + } +} +``` + + + + +:::tip Build it visually with the Data Mapper +The enriched `BankAccount` is assembled in code here, but the [Data Mapper](../../develop/integration-artifacts/supporting/data-mapper/data-mapper.md) can combine multiple inputs (the original request and the IBAN lookup result) into the target record visually. +::: + +## Complete sample + +The complete project is available in the [content enricher sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/content_enricher) on GitHub. diff --git a/en/docs/guides/patterns/content-filter.md b/en/docs/guides/patterns/content-filter.md new file mode 100644 index 00000000000..3f1493e647d --- /dev/null +++ b/en/docs/guides/patterns/content-filter.md @@ -0,0 +1,93 @@ +--- +title: Content Filter +description: "Implement the Content Filter pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Content Filter + +Use a Content Filter to simplify dealing with a large message when you are interested in only a few data items. The filter removes the unneeded elements and passes on a smaller message. + +In WSO2 Integrator, the content filter is a query expression whose `select` clause projects only the required fields, producing the reduced message the target system expects. + +## Example: Trimming reimbursement templates for payroll + +This example receives detailed reimbursement templates that include descriptive fields the payroll API does not accept. The content filter projects each template down to the `reimbursementTypeID` and `fixedAmount` fields before posting the reduced message to the Xero payroll endpoint. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `DetailedReimbursementTemplate[]` payload. +2. Add an HTTP client connection for the Xero API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add a data mapper (`filterReimbursements`) that maps `DetailedReimbursementTemplate` to `ReimbursementTemplate`, keeping only `reimbursementTypeID` and `fixedAmount`. This is the content filter. +4. Post the filtered list to the Xero pay template endpoint and return the result. + +The flow projects each reimbursement template down to the two fields the payroll API needs, then posts the reduced message: + + + +Because `filterReimbursements` is a datamapper function, WSO2 Integrator opens it in the visual [Data Mapper](../../develop/integration-artifacts/supporting/data-mapper/data-mapper.md). The input `DetailedReimbursementTemplate` carries three fields, but the output keeps only `reimbursementTypeID` and `fixedAmount`. `reimbursementTypeName` has no link, so it is dropped: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type DetailedReimbursementTemplate record { + string reimbursementTypeID; + string reimbursementTypeName; + float fixedAmount; +}; + +type ReimbursementTemplate record { + string reimbursementTypeID; + float fixedAmount; +}; + +type Reimbursement record { + string id; + record { + string reimbursementTypeID; + float fixedAmount; + }[] reimbursementTemplates; +}; + +final http:Client xero = check new ("http://api.xero.com.balmock.io"); +// docs-fold-end + +function filterReimbursements(DetailedReimbursementTemplate[] templates) returns ReimbursementTemplate[] => + from var templatesItem in templates + select { + reimbursementTypeID: templatesItem.reimbursementTypeID, + fixedAmount: templatesItem.fixedAmount + }; + +listener http:Listener httpListener = new (port = 8080); + +service /payroll on httpListener { + + resource function post employees/[string id]/paytemplate/reimbursements(DetailedReimbursementTemplate[] templates) + returns Reimbursement|error { + ReimbursementTemplate[] reimbursementRequests = filterReimbursements(templates); + Reimbursement result = check xero->/payrollxro/employees/[id]/paytemplate/reimbursements.post(reimbursementRequests); + return result; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [content filter sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/content_filter) on GitHub. diff --git a/en/docs/guides/patterns/document-message.md b/en/docs/guides/patterns/document-message.md new file mode 100644 index 00000000000..f4e1b559d0a --- /dev/null +++ b/en/docs/guides/patterns/document-message.md @@ -0,0 +1,81 @@ +--- +title: Document Message +description: "Implement the Document Message pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Document Message + +Use a Document Message to transfer a unit of data from one application to another. Unlike a command message, the sender does not dictate what the receiver should do with the data. The message simply delivers the document. + +In WSO2 Integrator, a document message is built by setting a file or structured payload on a request and transmitting it over a client connection. Retry configuration on the connection keeps the document delivery reliable. + +## Example: Bulk-uploading leads to a CRM + +This example transfers a CSV document of sales leads to the Zoho CRM bulk upload API. The flow picks the file from an FTP drop location, attaches it to the request as a multipart payload along with organization headers, and sends it over a connection configured with retries. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `CsvRequest` payload identifying the organization and file. +2. Add an HTTP client connection for the Zoho API with a retry configuration. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, build a request, add the organization and feature headers, and set the CSV file from the FTP incoming directory as a multipart payload. This is the document message. +4. Post the request to the Zoho bulk upload endpoint and return the `ZohoResponse`. + +The flow attaches the CSV file to the request and transfers it to the Zoho bulk-upload endpoint: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; +import ballerina/mime; + +type CsvRequest record {| + string org; + string filename; +|}; + +type ZohoResponse record {| + string status; + string code; + string message; + record {| + string file_id; + string created_time; + |} details; +|}; + +final http:Client zohoClient = check new ("http://content.zohoapis.com.balmock.io", retryConfig = {count: 3, interval: 1, statusCodes: [404, 408, 500]} +); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /crm on httpListener { + resource function post bulkUploadLeads(CsvRequest csvRequest) returns ZohoResponse|error { + http:Request request = new http:Request(); + () var1 = request.addHeader("X-CRM_ORG", csvRequest.org); + () var2 = request.addHeader("feature", "bulk-write"); + () var3 = request.setFileAsPayload("./ftpincoming/" + csvRequest.filename, contentType = mime:MULTIPART_FORM_DATA); + ZohoResponse result = check zohoClient->/crm/v5/upload.post(request); + return result; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [document message sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/document_message) on GitHub. diff --git a/en/docs/guides/patterns/event-message.md b/en/docs/guides/patterns/event-message.md new file mode 100644 index 00000000000..5cf5a5a009c --- /dev/null +++ b/en/docs/guides/patterns/event-message.md @@ -0,0 +1,78 @@ +--- +title: Event Message +description: "Implement the Event Message pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Event Message + +Use an Event Message to transmit a notification about something that happened in one application to other interested applications. The receiver reacts to the event; it does not return a result to the sender. + +In WSO2 Integrator, an event message is built from the details of the occurrence and dispatched over a client connection to the notification channel. The flow does not wait on a business response. + +## Example: Notifying incidents over SMS + +This example receives an incident report and transmits it as an event message: an SMS notification sent through the Twilio API. The message body describes what happened and when, and the subscriber's phone number identifies the interested receiver. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `IncidentRequest` payload. +2. Add an HTTP client connection for the Twilio API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, build the event text from the incident description, date, and time. +4. URL-encode the sender, recipient, and body fields, set them as a form-encoded payload, and post the event message to the Twilio messages endpoint. + +The flow builds the incident notification and sends it as an SMS event through Twilio: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; +import ballerina/mime; +import ballerina/url; + +type IncidentRequest record { + string phoneNo; + Incident incident; +}; + +type Incident record {| + string description; + string date; + string time; +|}; + +final http:Client twilio = check new ("http://api.twilio.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post incidents(IncidentRequest req) returns error? { + string body = string `Incident ${req.incident.description} reported: ${req.incident.date} at ${req.incident.time}.`; + http:Request twilioReq = new http:Request(); + string payload = "From=" + check url:encode("+15005550006", "utf-8") + + "&To=" + check url:encode(req.phoneNo, "utf-8") + + "&Body=" + check url:encode(body, "utf-8"); + () var1 = twilioReq.setTextPayload(payload, mime:APPLICATION_FORM_URLENCODED); + http:Response response = check twilio->/["2010-04-01"]/Accounts/["VBC1849a56d52g41s4b2b2cc004c0027aa8"]/Messages\.json.post(twilioReq, targetType = http:Response); + } +} +``` + + + + +## Complete sample + +The complete project is available in the [event message sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/event_message) on GitHub. diff --git a/en/docs/guides/patterns/format-indicator.md b/en/docs/guides/patterns/format-indicator.md new file mode 100644 index 00000000000..73ff0b37a80 --- /dev/null +++ b/en/docs/guides/patterns/format-indicator.md @@ -0,0 +1,109 @@ +--- +title: Format Indicator +description: "Implement the Format Indicator pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Format Indicator + +Use a Format Indicator to design a message's data format so it can evolve: each message carries a version indicator, and receivers use it to interpret the message correctly even after the format changes. + +In WSO2 Integrator, a format indicator is a discriminating field on a union of record types. Each record fixes its `version` field to a specific value, so the runtime binds the incoming message to the right format, and the flow branches on the resulting type. + +## Example: Accepting two versions of a patient record + +This example accepts patient data in two formats. Version `1.0` carries flat `firstName`/`lastName` fields, while version `2.0` nests a full `Patient` record. The `version` field in the payload is the format indicator: the service converts whichever version arrives into the current `Patient` format before forwarding it. + + + + +1. Define the `PatientReqV1` and `PatientReqV2` record types, each with a fixed `version` field, and the `PatientReq` union type. +2. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts a `PatientReq` payload. +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) that checks whether the request is a `PatientReqV1`. +4. In each branch, map the versioned request into the current `Patient` format. +5. Post the converted patient to the downstream patient service connection. + +The flow branches on the message version: version 1.0 and version 2.0 are each mapped into the current `Patient` format before being forwarded: + + + +The [type diagram](../../develop/understand-ide/editors/type-diagram-editor.md) shows the format indicator at work: the `PatientReq` union accepts either `PatientReqV1` or `PatientReqV2` (each fixing its `version` field), and both resolve to the common `Patient` format: + + + +The version-1 conversion is written as the datamapper function `toPatient`, so WSO2 Integrator opens it in the visual [Data Mapper](../../develop/integration-artifacts/supporting/data-mapper/data-mapper.md): `dob` and `diagnosis` map straight across, while `firstName` and `lastName` combine into `fullName`, a [many-to-one mapping](../../develop/integration-artifacts/supporting/data-mapper/mapping-capabilities.md#many-to-one-mapping): + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type PatientReqV1 record {| + "1.0" version = "1.0"; + string firstName; + string lastName; + string dob; + string diagnosis; +|}; + +type PatientReqV2 record {| + "2.0" version = "2.0"; + Patient patient; +|}; + +type PatientReq PatientReqV1|PatientReqV2; + +type Patient record {| + string fullName; + string dob; + string diagnosis; +|}; + +final http:Client patientClient = check new ("http://api.patients.com.balmock.io"); +// docs-fold-end + +function toPatient(PatientReqV1 req) returns Patient => { + dob: req.dob, + fullName: req.firstName + " " + req.lastName, + diagnosis: req.diagnosis +}; + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post data/patient(PatientReq patientReq) returns error? { + Patient patient; + if patientReq is PatientReqV1 { + patient = toPatient(patientReq); + } else { + patient = { + dob: patientReq.patient.dob, + fullName: patientReq.patient.fullName, + diagnosis: patientReq.patient.diagnosis + }; + } + http:Response response = check patientClient->/patient.post(patient, targetType = http:Response); + } + + resource function post patient(Patient patient) returns error? { + http:Response response = check patientClient->/patient.post(patient, targetType = http:Response); + } +} +``` + + + + +## Complete sample + +The complete project is available in the [format indicator sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/format_indicator) on GitHub. diff --git a/en/docs/guides/patterns/idempotent-receiver.md b/en/docs/guides/patterns/idempotent-receiver.md new file mode 100644 index 00000000000..c916b555168 --- /dev/null +++ b/en/docs/guides/patterns/idempotent-receiver.md @@ -0,0 +1,77 @@ +--- +title: Idempotent Receiver +description: "Implement the Idempotent Receiver pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Idempotent Receiver + +Use an Idempotent Receiver so the receiver can safely handle duplicate messages: processing the same message more than once has the same effect as processing it once. + +In WSO2 Integrator, the receiver keeps track of what it has already processed, keyed by the message identifier, and skips the side effect when the incoming message matches the recorded state. + +## Example: Deduplicating order status updates + +This example receives order status updates that may be delivered more than once. The receiver records the last status per order ID; when an update arrives with a status it has already applied, it acknowledges the duplicate with `204 No Content` instead of processing it again, and applies genuinely new statuses with `201 Created`. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `put` resource keyed by the `orderId` path parameter. +2. In the flow, look up the recorded status for the order in the status map. +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) comparing the recorded status with the incoming status. +4. When they match, return `204 No Content`, acknowledging the duplicate without reprocessing. Otherwise record the new status and return `201 Created`. + +The flow compares the incoming status with the last recorded status: a duplicate returns `204 No Content`, while a new status is recorded and returns `201 Created`: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type OrderDetail record { + string orderId; + OrderStatus status; +}; + +enum OrderStatus { + CREATED, + SHIPPED, + COMPLETED, + CANCELLED +} +// docs-fold-end + +map orderStatuses = {}; + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function put manage\-orders/[string orderId](OrderDetail orderDetail) returns http:STATUS_NO_CONTENT|http:STATUS_CREATED { + "CANCELLED"|"COMPLETED"|"SHIPPED"|"CREATED"|() orderStatus = orderStatuses[orderId]; + if orderStatus == orderDetail.status { + return http:STATUS_NO_CONTENT; + } else { + orderStatuses[orderId] = orderDetail.status; + return http:STATUS_CREATED; + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [idempotent receiver sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/idempotent_receiver) on GitHub. diff --git a/en/docs/guides/patterns/message-dispatcher.md b/en/docs/guides/patterns/message-dispatcher.md deleted file mode 100644 index a0b5420a4d0..00000000000 --- a/en/docs/guides/patterns/message-dispatcher.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Message Dispatcher -description: "Implement the Message Dispatcher pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Message Dispatcher - -Use the Message Dispatcher pattern to coordinate which performer receives each message when several equivalent performers can process the same request. - -The pattern is implemented at the point where the integration receives a message and must choose one processing endpoint. Keep the dispatch state close to the entry point, update it before the outbound call, and send the message to the selected performer through a connector. - -## Stateful round-robin dispatch - -Use stateful round-robin dispatch when each incoming message should be sent to the next processor in a fixed set. Store the current processor index in the service, update it with a `lock`, and call the selected processor through an [HTTP client connection](../../connectors/catalog/built-in/http/action-reference.md#client). The `lock` keeps the index update consistent when multiple requests arrive at the same time. For constructs that do not have a full visual representation, switch to pro-code through the [Flow Diagram editor](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#configuring-a-node). - - - - -1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) for the dispatcher entry point. -2. Add a `GET` resource, such as `/process`, and define a query parameter that carries the message reference, such as `resourceUrl`. See [resource inputs](../../develop/integration-artifacts/service/http.md#defining-inputs). -3. Add the outbound processor [HTTP connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). Configure its base URL with a [configurable variable](../../reference/config/configuration-management.md#configurable-variables). -4. Add a service-level variable named `nextProcessor` with type `int` and default value `0`. -5. In the resource flow, add the processor selection logic as a Ballerina code block: read `nextProcessor`, advance it inside a `lock`, and store the selected processor ID. -6. Add the HTTP connector call that includes the selected processor ID in the request path and passes the message reference as a query parameter. -7. Return the processor response from the resource function. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; - -configurable string processorEndpoint = ?; - -type ProcessingResponse record {| - string[] lines; - int lineCount; - string sourceUrl; -|}; - -final http:Client processorClient = check new (processorEndpoint); -final readonly & string[] processorIds = ["processor1", "processor2", "processor3"]; -// docs-fold-end - -service / on new http:Listener(8080) { - int nextProcessor = 0; - - isolated resource function get process(string resourceUrl) - returns ProcessingResponse|error { - int currentProcessor; - lock { - currentProcessor = self.nextProcessor; - self.nextProcessor = currentProcessor == processorIds.length() - 1 - ? 0 - : currentProcessor + 1; - } - - string processorId = processorIds[currentProcessor]; - return check processorClient->/[processorId]/process(resourceUrl = resourceUrl); - } -} -``` - - - diff --git a/en/docs/guides/patterns/message-endpoint.md b/en/docs/guides/patterns/message-endpoint.md new file mode 100644 index 00000000000..d0493daaa23 --- /dev/null +++ b/en/docs/guides/patterns/message-endpoint.md @@ -0,0 +1,66 @@ +--- +title: Message Endpoint +description: "Implement the Message Endpoint pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Message Endpoint + +A Message Endpoint connects an application to a messaging channel so it can send and receive messages, keeping the application code separate from the mechanics of the channel. + +In WSO2 Integrator, a service and its listener form the receiving endpoint: the listener handles the channel mechanics, and the typed resource signature delivers only the message data to the application logic. Client connections form the sending endpoint. + +## Example: Currency conversion endpoint + +This example exposes a currency conversion application through an HTTP endpoint. The listener and resource function isolate the conversion logic from the transport, so the application works with typed `Currency` values and a `decimal` amount, not with raw protocol details. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) on a listener. This is the message endpoint that connects the application to the channel. +2. Add a `get` resource that accepts the base currency, target currency, and amount as typed query parameters. +3. In the flow, look up the exchange rates and compute the converted amount. +4. Return the converted value from the resource. + +The flow receives the typed request, looks up the exchange rates, and returns the converted amount. The listener and resource isolate this logic from the transport: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type Currency "AUD"|"INR"|"GBP"; +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1/rates on httpListener { + isolated resource function get convert(Currency base, Currency target, decimal amount = 1.00) returns decimal { + map rates = { + "AUD": 1.59, + "INR": 83.24, + "GBP": 0.83 + }; + decimal baseUsdValue = rates.get(base); + decimal targetUsdValue = rates.get(target); + return (targetUsdValue / baseUsdValue) * amount; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [message endpoint sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_endpoint) on GitHub. diff --git a/en/docs/guides/patterns/message-filter.md b/en/docs/guides/patterns/message-filter.md index 03672ddd82d..2aa1f9ee34a 100644 --- a/en/docs/guides/patterns/message-filter.md +++ b/en/docs/guides/patterns/message-filter.md @@ -1,5 +1,4 @@ --- -sidebar_position: 9 title: Message Filter description: "Implement the Message Filter pattern with WSO2 Integrator." --- @@ -7,126 +6,31 @@ description: "Implement the Message Filter pattern with WSO2 Integrator." import TabItem from '@theme/TabItem'; import { EipReferenceLink, - PatternImplementationTabs, PatternImage, + PatternImplementationTabs, } from '@site/src/utils/eipPatternComponents'; # Message Filter -Use the Message Filter pattern to evaluate each incoming message and continue the flow only for messages that satisfy the selected condition. - -The pattern is implemented by placing a filtering construct at the point where the integration has enough context to decide whether a message should continue. Use flow-level constructs when the decision depends on data the integration reads or derives. Use boundary or source-level constructs when the decision can be made from metadata or delivery rules before the main processing path starts. - -## Predicate-based filtering - -Use predicate-based filtering with [if/else statements](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) when each message carries the fields needed for a single boolean decision, such as priority, source, header, or status. The accepted path contains the forwarding or processing action. The rejected path does nothing or handles the rejection separately. - - - - -1. Open the flow and [add a step](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#anatomy-of-the-editor). -2. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) at the point where the message has enough data for the decision. -3. Set the condition to `message.priority == HIGH_PRIORITY`. -4. Add the accepted action inside the matching branch. -5. Leave the other branch empty when unmatched messages should be discarded. - - - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; - -const HIGH_PRIORITY = 1; -const MEDIUM_PRIORITY = 2; -const LOW_PRIORITY = 3; - -type Message record {| - string id; - string source; - string subject; - HIGH_PRIORITY|MEDIUM_PRIORITY|LOW_PRIORITY priority; -|}; - -final http:Client outboundChannel = check new ("http://api.outbound.channel.com.balmock.io"); -// docs-fold-end +Use a Message Filter so a component avoids receiving uninteresting messages: messages that match the condition continue through the flow, and all others are discarded. -service /api/v1 on new http:Listener(8080) { - resource function post message(Message message) returns error? { - if message.priority == HIGH_PRIORITY { - _ = check outboundChannel->/messages.post(message, targetType = http:Response); - } - } -} -``` +In WSO2 Integrator, the filter is an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) with no else path. When the condition fails, the flow simply ends and the message goes no further. - - +## Example: Forwarding only high-priority tickets -## Collection-level filtering - -Use collection-level filtering with [query expressions](../../reference/language/query-expressions.md) when the flow already has a group of messages or records and only a subset should continue. Keep the predicate in the `where` clause so the result is the accepted collection. +This example receives support tickets and notifies the support channel only for priority 1 tickets. Lower-priority tickets are dropped by the filter, so the notification channel never receives them. -1. Open the flow and [add a step](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#anatomy-of-the-editor). -2. Add a [Map Data or Declare Variable step](../../reference/language/query-expressions.md). -3. Set the output type to the accepted collection type, such as `Message[]`. -4. Enter a query expression with a `where` clause for the filter predicate. -5. Use the resulting collection in the next processing or forwarding step. - - - - - - -```ballerina -// docs-fold-start: Supporting definitions -const HIGH_PRIORITY = 1; -const MEDIUM_PRIORITY = 2; -const LOW_PRIORITY = 3; - -type Message record {| - string id; - string source; - string subject; - HIGH_PRIORITY|MEDIUM_PRIORITY|LOW_PRIORITY priority; -|}; -// docs-fold-end - -function filterHighPriorityMessages(Message[] messages) returns Message[] { - return from Message message in messages - where message.priority == HIGH_PRIORITY - select message; -} -``` +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `Ticket` payload. +2. Add an HTTP client connection for the notification channel. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) with the condition `ticket.priority == 1` and no else branch. +4. Inside the **True** branch, post the ticket to the notification endpoint. Tickets that fail the condition are discarded. - - - -## Boundary-level filtering - -Use boundary-level filtering when the input artifact can reject or route messages before custom flow logic runs. For HTTP-facing inputs, use a [request interceptor](../../connectors/catalog/built-in/http/trigger-reference.md#interceptors) when the decision can be made from request metadata before the resource executes. Other inputs can use their own handler, listener, or subscription selection points. - - - +The flow continues to the notification channel only when the ticket priority is 1; tickets that fail the condition simply end: -1. Add the source artifact, such as an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service). -2. Add a request interceptor for the service boundary. -3. Read the request metadata needed for the filter, such as a priority header. -4. Return a response for messages that should stop at the boundary. -5. Call the next service only for messages that should enter the resource flow. + @@ -135,41 +39,23 @@ Use boundary-level filtering when the input artifact can reject or route message // docs-fold-start: Supporting definitions import ballerina/http; -type OrderCreated record {| +type Ticket record {| string id; - string priority; + string url; + string subject; + 1|2|3 priority; |}; -function processOrder(OrderCreated event) returns error? { -} +final http:Client notificationChannel = check new ("http://api.notification.channel.com.balmock.io"); // docs-fold-end -listener http:Listener eventListener = new (8080, - interceptors = [new HighPriorityFilter()] -); - -service class HighPriorityFilter { - *http:RequestInterceptor; - - resource function 'default [string... path]( - http:RequestContext ctx, http:Request req) - returns http:NextService|http:Accepted|error? { - if !req.hasHeader("x-priority") { - return {body: {status: "filtered"}}; - } +listener http:Listener httpListener = new (port = 8080); - string priority = check req.getHeader("x-priority"); - if priority != "high" { - return {body: {status: "filtered"}}; +service /api/v1 on httpListener { + resource function post ticket(Ticket ticket) returns error? { + if ticket.priority == 1 { + http:Response response = check notificationChannel->/email/notify.post(ticket, targetType = http:Response); } - - return ctx.next(); - } -} - -service /events on eventListener { - resource function post orders(OrderCreated event) returns error? { - check processOrder(event); } } ``` @@ -177,59 +63,6 @@ service /events on eventListener { -## Broker-side delivery filtering - -Use broker-side delivery filtering when RabbitMQ can reduce what reaches the flow before consumption. Route matching messages into a dedicated queue with a direct exchange and binding key, then configure the RabbitMQ trigger to consume only that queue. Use [RabbitMQ exchange bindings](../../connectors/catalog/messaging/rabbitmq/actions.md#exchange-management) to bind the accepted-message queue to the exchange with the accepted routing key. +## Complete sample - - - -1. Add the [RabbitMQ event integration](../../develop/integration-artifacts/event/rabbitmq.md#creating-a-rabbitmq-service). -2. Configure the RabbitMQ trigger connection with the broker host and port. -3. Set **Queue Name** to the queue that receives accepted messages, such as `high-priority-orders`. -4. Create the RabbitMQ broker resources with connector actions or broker administration: declare a direct exchange, declare the accepted-message queue, and bind the queue to the exchange with the accepted routing key. -5. Publish messages to that exchange with the routing key that matches the accepted binding key, such as `orders.priority.high`. -6. Add processing steps only for messages delivered to the accepted-message queue. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerinax/rabbitmq; - -configurable string rabbitmqHost = "localhost"; -configurable int rabbitmqPort = 5672; -const ORDERS_EXCHANGE = "orders.events"; -const ACCEPTED_ORDERS_QUEUE = "high-priority-orders"; -const ACCEPTED_ORDERS_BINDING_KEY = "orders.priority.high"; - -listener rabbitmq:Listener rabbitmqListener = check new (rabbitmqHost, rabbitmqPort); -rabbitmq:Client rabbitmqClient = check new (rabbitmqHost, rabbitmqPort); - -function configureBrokerDeliveryFilter() returns error? { - check rabbitmqClient->exchangeDeclare(ORDERS_EXCHANGE, rabbitmq:DIRECT_EXCHANGE, config = { - durable: true - }); - check rabbitmqClient->queueDeclare(ACCEPTED_ORDERS_QUEUE, config = { - durable: true - }); - check rabbitmqClient->queueBind(ACCEPTED_ORDERS_QUEUE, ORDERS_EXCHANGE, ACCEPTED_ORDERS_BINDING_KEY); -} - -function processOrder(rabbitmq:AnydataMessage message) returns error? { -} -// docs-fold-end - -@rabbitmq:ServiceConfig { - queueName: ACCEPTED_ORDERS_QUEUE -} -service rabbitmq:Service on rabbitmqListener { - remote function onMessage(rabbitmq:AnydataMessage message) returns error? { - check processOrder(message); - } -} -``` - - - +The complete project is available in the [message filter sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_filter) on GitHub. diff --git a/en/docs/guides/patterns/message-mapper.md b/en/docs/guides/patterns/message-mapper.md deleted file mode 100644 index 56e73d0173e..00000000000 --- a/en/docs/guides/patterns/message-mapper.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Message Mapper -description: "Implement the Message Mapper pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Message Mapper - -Use the Message Mapper pattern to keep domain records independent from channel-specific message records by placing conversion logic in a dedicated mapper. - -The pattern is implemented between the message boundary and the domain processing logic. Convert incoming message payloads into domain records before the flow applies business logic, and convert domain records into channel-specific message records before the flow sends them to another endpoint. - -## Record-to-record mapping - -Use record-to-record mapping when both the domain value and the channel payload can be represented as typed records. Create separate record types for the domain model and the message format, then keep the mapping in a [reusable data mapper](../../develop/integration-artifacts/supporting/data-mapper/access-paths/reusable.md) or a dedicated mapper function. Use [mapping capabilities](../../develop/integration-artifacts/supporting/data-mapper/mapping-capabilities.md) for field connections, expressions, and custom transformation logic. - - - - -1. Define separate record types for the domain value and the channel message. See [Types](../../develop/integration-artifacts/supporting/types.md). -2. Create a [reusable data mapper](../../develop/integration-artifacts/supporting/data-mapper/access-paths/reusable.md) with the domain record as the input and the message record as the output. -3. Open the data mapper canvas and connect matching fields, such as `id` to `orderId`. -4. Use the expression editor for transformed fields, such as combining names or calculating a total. See [Expression editor](../../develop/integration-artifacts/supporting/data-mapper/mapping-capabilities.md#expression-editor). -5. Use [array mappings](../../develop/integration-artifacts/supporting/data-mapper/array-mappings/array-mappings.md) when the mapper must convert item collections. -6. Add a **Map Data** step in the flow and pass the mapped record to the next service, resource function, or connector call. - - - - -```ballerina -// docs-fold-start: Supporting definitions -type Customer record {| - string name; - string email; -|}; - -type LineItem record {| - string sku; - int quantity; - decimal unitPrice; -|}; - -type Order record {| - string id; - Customer customer; - LineItem[] items; -|}; - -type OrderMessage record {| - string orderId; - string customerName; - string customerEmail; - decimal total; - LineItem[] items; -|}; -// docs-fold-end - -function toMessage(Order order) returns OrderMessage { - return { - orderId: order.id, - customerName: order.customer.name, - customerEmail: order.customer.email, - total: from var {unitPrice, quantity} in order.items - let decimal itemTotal = unitPrice * quantity - collect sum(itemTotal), - items: order.items - }; -} - -function toDomain(OrderMessage message) returns Order { - return { - id: message.orderId, - customer: { - name: message.customerName, - email: message.customerEmail - }, - items: message.items - }; -} -``` - - - - -## Data-format boundary mapping - -Use data-format boundary mapping when the channel sends or receives raw JSON, XML, CSV, or another serialized format. Keep parsing and serialization at the boundary, then call the typed mapper so the main flow works with records instead of raw payloads. For JSON payloads, use [type-safe JSON conversion](../../develop/transform/json.md#convert-a-json-value-to-a-typed-record). For XML and CSV payloads, use the corresponding [XML processing](../../develop/transform/xml.md) or [CSV and flat file processing](../../develop/transform/csv-flat-file.md) guide. - - - - -1. Define the message record type that matches the incoming raw payload. -2. For a JSON boundary, add a **Call Function** step for `jsondata:parseAsType` and set the result type to the message record. -3. Add a **Map Data** step that converts the message record into the domain record. -4. Add the domain processing steps after the mapper. -5. Before sending a raw response or outbound message, map the domain record back to the channel message record. -6. Add a **Call Function** step for `jsondata:toJson` only when the outbound endpoint requires a raw JSON payload. - - - - -```ballerina -import ballerina/data.jsondata; - -// docs-fold-start: Supporting definitions -type Customer record {| - string name; - string email; -|}; - -type LineItem record {| - string sku; - int quantity; - decimal unitPrice; -|}; - -type Order record {| - string id; - Customer customer; - LineItem[] items; -|}; - -type OrderMessage record {| - string orderId; - string customerName; - string customerEmail; - decimal total; - LineItem[] items; -|}; - -function toMessage(Order order) returns OrderMessage { - return { - orderId: order.id, - customerName: order.customer.name, - customerEmail: order.customer.email, - total: from var {unitPrice, quantity} in order.items - let decimal itemTotal = unitPrice * quantity - collect sum(itemTotal), - items: order.items - }; -} - -function toDomain(OrderMessage message) returns Order { - return { - id: message.orderId, - customer: { - name: message.customerName, - email: message.customerEmail - }, - items: message.items - }; -} -// docs-fold-end - -function readIncoming(json payload) returns Order|error { - OrderMessage message = check jsondata:parseAsType(payload); - return toDomain(message); -} - -function writeOutgoing(Order order) returns json { - OrderMessage message = toMessage(order); - return jsondata:toJson(message); -} -``` - - - diff --git a/en/docs/guides/patterns/message-router.md b/en/docs/guides/patterns/message-router.md new file mode 100644 index 00000000000..2de152b702f --- /dev/null +++ b/en/docs/guides/patterns/message-router.md @@ -0,0 +1,92 @@ +--- +title: Message Router +description: "Implement the Message Router pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Message Router + +Use a Message Router to decouple individual processing steps so each message is passed to a different destination depending on a set of conditions, without the sender knowing which destination handles it. + +In WSO2 Integrator, the router is the decision construct in the flow (an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) or a [Match node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#match)) that selects which connection or processing path receives the message. + +## Example: Routing shipment tracking requests + +This example exposes a shipment tracking service that routes each request to a different DHL tracking API depending on the destination country. Requests for the UK are routed to the DHL Parcel UK API, while other requests are routed to the DHL Deutsche Post International API. The caller uses one endpoint and never sees the routing decision. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `get` resource that accepts the country and tracking number as path parameters. +2. Add an HTTP client connection for the DHL API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) with the condition `country is UK`. +4. In the **True** branch, call the DHL Parcel UK tracking endpoint and return the shipment status. +5. In the **False** branch, call the DHL Deutsche Post International tracking endpoint and return the event status. + +The flow routes each request by destination country: UK to the DHL Parcel UK API, others to the DHL Deutsche Post International API: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type DhlUkResponse record {| + string url; + ShipmentData[] shipments; +|}; + +type ShipmentData record {| + string id; + Status status; +|}; + +type DhlDpiResponse record {| + Status[] events; + string publicUrl; + string barcode; +|}; + +type Status record {| + string statusCode; + string status; +|}; + +enum Country { + UK, + DE +} + +final http:Client dhl = check new ("http://api.dhl.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /shipments on httpListener { + resource function get [Country country]/[string trackingNumber]/status() returns string|error { + if country is UK { + DhlUkResponse response = check dhl->/parceluk/tracking/v1/shipments.get(trackingNumber = trackingNumber); + return response.shipments[0].status.status; + } else { + DhlDpiResponse response = check dhl->/dpi/tracking/v1/trackings/[trackingNumber].get(); + return response.events[0].status; + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [message router sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_router) on GitHub. diff --git a/en/docs/guides/patterns/message-sequence.md b/en/docs/guides/patterns/message-sequence.md new file mode 100644 index 00000000000..0712db48c91 --- /dev/null +++ b/en/docs/guides/patterns/message-sequence.md @@ -0,0 +1,67 @@ +--- +title: Message Sequence +description: "Implement the Message Sequence pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Message Sequence + +Use a Message Sequence to transmit an arbitrarily large amount of data as a sequence of smaller messages, with sequence information that lets the receiver reassemble the whole. + +In WSO2 Integrator, a [Foreach node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#foreach) drives the sequence: each iteration requests or sends one chunk, and the loop index is the sequence identifier that orders the chunks. + +## Example: Downloading a large file in chunks + +This example downloads a large file from Amazon S3 as a sequence of ranged requests. The flow first reads the file size from the object metadata, computes the number of chunks, and then requests each 10-byte range in order, appending every chunk to a local file to reassemble the document. + + + + +1. Create an [automation](../../develop/integration-artifacts/automation.md#creating-an-automation) to run the flow. +2. Add an HTTP client connection for the S3 bucket. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, send a `head` request to read the `Content-Length` header and compute the number of chunks. +4. Add a [Foreach node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#foreach) over the chunk indexes. In each iteration, set the `Range` header for the chunk, request that range, and append the bytes to the local file. + +The **Foreach** node requests the file one byte-range at a time, appending each chunk to reassemble the whole: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; +import ballerina/io; + +final http:Client s3Client = check new ("http://noname-tech.s3.amazonaws.com.balmock.io"); +// docs-fold-end + +public function main() returns error? { + http:Response metaData = check s3Client->/employee_names.head(); + int fileSize = check int:fromString(check metaData.getHeader("Content-Length")); + + () var3 = check io:fileWriteBytes("./resources/employee_names.txt", []); + + int numberOfChunks = (fileSize + 10 - 1) / 10; + foreach int i in 0 ..< numberOfChunks { + map headers = {Range: string `bytes=${10 * i}-${10 * (i + 1) - 1}`}; + http:Response s3Response = check s3Client->/employee_names.get(headers); + byte[] chunkData = check s3Response.getBinaryPayload(); + () var2 = check io:fileWriteBytes("./resources/employee_names.txt", chunkData, io:APPEND); + } +} +``` + + + + +## Complete sample + +The complete project is available in the [message sequence sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_sequence) on GitHub. diff --git a/en/docs/guides/patterns/message-store.md b/en/docs/guides/patterns/message-store.md new file mode 100644 index 00000000000..7f02eab001b --- /dev/null +++ b/en/docs/guides/patterns/message-store.md @@ -0,0 +1,78 @@ +--- +title: Message Store +description: "Implement the Message Store pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Message Store + +Use a Message Store to capture message information in a central location without disturbing the loosely coupled and transient nature of the messaging system. The flow stores a copy of each message it processes, so the data can be queried later. + +In WSO2 Integrator, the store is an external datastore written to asynchronously with `start`, so persisting the message never blocks or slows down the main message flow. + +## Example: Storing geocoding results + +This example resolves street addresses to coordinates. The flow first checks the Firebase message store for a previously stored result and returns it if present. On a miss, it calls the Google Geocoding API, returns the result to the caller immediately, and stores the response in Firebase asynchronously for future requests. + + + + +The design view shows the two systems involved: the Google geocoding service the data comes from, and the Firebase store the messages are written to: + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `get` resource that accepts the address as a query parameter. +2. Add HTTP client connections for the Google Geocoding API and the Firebase datastore. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, read the stored result for the address from Firebase and return it when found. +4. On a store miss, call the geocoding API, start the `storeAddress` function asynchronously to persist the response, and return the geocode to the caller without waiting for the store write. + +The flow returns a stored result when one exists; otherwise it calls the geocoding API, returns immediately, and stores the result asynchronously with `start`: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type GeoCodeResponse record {| + json results; +|}; + +final http:Client geoCodingClient = check new ("http://api.maps.googleapis.com.balmock.io"); +final http:Client firebaseClient = check new ("http://api.mapsproject.firebase.com.balmock.io"); +// docs-fold-end + +function storeAddress(string address, GeoCodeResponse geocode) returns error? { + json jsonResult = check firebaseClient->/location/[address]/location\.json.put(geocode, targetType = json); +} + +listener http:Listener httpListener = new (port = 8080); + +service /api on httpListener { + resource function get location(string address) returns GeoCodeResponse|error { + GeoCodeResponse|error storedGeocode = firebaseClient->/location/[address]/location\.json(); + if storedGeocode !is error { + return storedGeocode; + } + GeoCodeResponse geocode = check geoCodingClient->/maps/api/geocode/'json.get(place = address); + future futureResult = start storeAddress(address, geocode); + return geocode; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [message store sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_store) on GitHub. diff --git a/en/docs/guides/patterns/message-translator.md b/en/docs/guides/patterns/message-translator.md new file mode 100644 index 00000000000..e9bb22745a2 --- /dev/null +++ b/en/docs/guides/patterns/message-translator.md @@ -0,0 +1,106 @@ +--- +title: Message Translator +description: "Implement the Message Translator pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Message Translator + +Use a Message Translator so that systems using different data formats can communicate with each other through messaging. The translator sits between the two systems and converts one message format into the other. + +In WSO2 Integrator, the translator is a data mapping function. The source and target message shapes are Ballerina records, and the mapping function converts one record into the other before the message is sent on. + +## Example: Translating sales data into invoices + +This example accepts sales opportunity data from a CRM-style analytics endpoint and translates it into the invoice format that the QuickBooks accounting API expects. The `translate` function is the message translator: it maps `SalesData` into a `QuickBooksInvoice` before forwarding it. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `SalesData` payload. +2. Add an HTTP client connection for the QuickBooks API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. [Add a data mapper](../../develop/integration-artifacts/supporting/data-mapper/access-paths/reusable.md) that maps `SalesData` to `QuickBooksInvoice`, converting each opportunity into an invoice entry. +4. Call the QuickBooks connection with the translated message. + +The flow calls the `translate` function to convert the sales data into the QuickBooks invoice format, then posts it: + + + +The [Data Mapper](../../develop/integration-artifacts/supporting/data-mapper/data-mapper.md) gives this conversion a visual view, which comes in handy for message translations: the `SalesData` fields on the left link to the `QuickBooksInvoice` fields on the right, and an [array mapping](../../develop/integration-artifacts/supporting/data-mapper/array-mappings/array-mappings.md) turns each `Opportunity` into an `Invoice`: + + + +Expanding both arrays to their element level shows the field correspondence inside the mapping: each `Opportunity`'s `id`, `amount`, and `closeDate` becomes the `Invoice` item's `id`, `amount`, and `invoiceDate`: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type SalesData record {| + Customer customer; + Opportunity[] opportunities; +|}; + +type Customer record {| + string id; + string name; + string email; +|}; + +type Opportunity record {| + string id; + decimal amount; + string closeDate; +|}; + +type QuickBooksInvoice record {| + string customerId; + Invoice[] invoices; +|}; + +type Invoice record {| + string id; + decimal amount; + string invoiceDate; +|}; + +final http:Client quickBooks = check new ("http://api.quickbooks.com.balmock.io"); +// docs-fold-end + +function translate(SalesData salesData) returns QuickBooksInvoice => { + customerId: salesData.customer.id, + invoices: from var opportunity in salesData.opportunities + select { + id: opportunity.id, + amount: opportunity.amount, + invoiceDate: opportunity.closeDate + } +}; + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1/analytics on httpListener { + resource function post sales(SalesData salesData) returns error? { + QuickBooksInvoice quickBooksInvoice = translate(salesData); + http:Response response = check quickBooks->/v3/company/REALM012/invoice.post(quickBooksInvoice, targetType = http:Response); + } +} +``` + + + + +## Complete sample + +The complete project is available in the [message translator sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message_translator) on GitHub. diff --git a/en/docs/guides/patterns/message.md b/en/docs/guides/patterns/message.md index 32c9c36bca1..6ea6bb96f1f 100644 --- a/en/docs/guides/patterns/message.md +++ b/en/docs/guides/patterns/message.md @@ -6,76 +6,36 @@ description: "Implement the Message pattern with WSO2 Integrator." import TabItem from '@theme/TabItem'; import { EipReferenceLink, + PatternImage, PatternImplementationTabs, } from '@site/src/utils/eipPatternComponents'; # Message -Use the Message pattern to package information into a structure that can move through a message channel without losing the distinction between system metadata and business data. +A Message packages a piece of information as a data record so it can be transmitted through a message channel from one application to another. -The pattern is implemented by defining the message shape close to the integration logic and binding it to a transport only at the channel boundary. Use a typed envelope when the application needs a transport-independent message. Use native protocol or connector bindings when the message is already tied to HTTP headers, payloads, or a broker-specific record. +In WSO2 Integrator, a message is modeled as a Ballerina record. The record type defines the structure of the data both applications agree on, and a client connection transmits the record over the channel. -## Typed message envelope +## Example: Updating a survey -Use a typed message envelope when the integration needs a stable application-level message shape before it sends data to a connector or returns it from a resource function. Model the envelope as a closed record with separate records for headers and body fields. +This example builds a message that carries the details of a customer satisfaction survey update and sends it to the SurveyMonkey API. The `SurveyUpdateRequest` record defines the message structure, and the HTTP client transmits it as a `PUT` request. -1. Create a new integration in WSO2 Integrator. -2. Add the header, body, and envelope records in [Types](../../develop/integration-artifacts/supporting/types.md). -3. Open the flow and [add a step](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#anatomy-of-the-editor). -4. Add a **Declare Variable** or **Map Data** step to construct the envelope. -5. Pass the envelope to the connector call, return it from the resource function, or map it into another boundary-specific message. +1. Define the message structure as a record type with the [Type editor](../../develop/understand-ide/editors/type-editor.md). Here, `SurveyUpdateRequest` names the fields the two applications agree on, with a type for each. +2. Create an [automation](../../develop/integration-artifacts/automation.md#creating-an-automation) to run the flow. +3. Add an HTTP client connection that points to the SurveyMonkey API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +4. In the flow, declare a variable of type `SurveyUpdateRequest` and assign the survey details. This record instance is the message. +5. Add the HTTP `put` action on the connection to transmit the message to the survey resource path. - - - -```ballerina -type OrderHeaders record {| - string correlationId; - string source; - string messageType; -|}; - -type OrderBody record {| - string orderId; - decimal amount; - string currency; -|}; - -type OrderMessage record {| - OrderHeaders headers; - OrderBody body; -|}; - -function buildOrderMessage(OrderBody order, string correlationId) returns OrderMessage { - return { - headers: { - correlationId, - source: "order-service", - messageType: "OrderCreated" - }, - body: order - }; -} -``` - - - +Defining the message is the heart of this pattern. The `SurveyUpdateRequest` record fixes the shape both applications rely on (`title`, `from_template_id`, `footer`, `folder_id`, and `theme_id`), each with its type: -## Channel boundary binding + -Use channel boundary binding when the message arrives through, or leaves through, a protocol that already provides metadata and payload locations. Keep the EIP Message shape explicit in the flow, then map HTTP headers, HTTP payloads, or broker records into that envelope at the boundary. +With the message type defined, the flow assigns its values and sends it to the SurveyMonkey channel as a single PUT request: - - - -1. Add and configure the required connector under **Connections**. For brokered messages, select the relevant connector, such as the [NATS connector](../../connectors/catalog/messaging/nats/connector-overview.md), from the [messaging connector catalog](../../connectors/catalog/index.mdx). -2. Add the listener or entry point for the inbound channel. For HTTP, start by [creating an HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service). -3. Bind the request payload as the body and bind transport metadata, such as headers, as message headers. -4. Add a **Map Data** step to create the typed message envelope from the inbound payload and metadata. -5. Publish the envelope through the connector, forward it to another channel, or return it from the resource function. + @@ -83,49 +43,33 @@ Use channel boundary binding when the message arrives through, or leaves through ```ballerina // docs-fold-start: Supporting definitions import ballerina/http; -import ballerinax/nats; - -final nats:Client orderEvents = check new (nats:DEFAULT_URL); - -type OrderHeaders record {| - string correlationId; - string source; - string messageType; -|}; - -type OrderBody record {| - string orderId; - decimal amount; - string currency; -|}; - -type OrderMessage record {| - OrderHeaders headers; - OrderBody body; -|}; + +type SurveyUpdateRequest record { + string title; + string from_template_id; + boolean footer; + string folder_id; + int theme_id; +}; + +final http:Client surveyMonkey = check new ("http://api.surveymonkey.com.balmock.io"); // docs-fold-end -service /orders on new http:Listener(8080) { - - resource function post .( - @http:Header {name: "x-correlation-id"} string correlationId, - OrderBody payload) returns OrderMessage|error { - OrderMessage message = { - headers: { - correlationId, - source: "http-api", - messageType: "OrderCreated" - }, - body: payload - }; - check orderEvents->publishMessage({ - subject: "orders.created", - content: message - }); - return message; - } +public function main() returns error? { + SurveyUpdateRequest message = { + title: "Customer Satisfaction Survey 2025", + from_template_id: "customer_satisfaction_template_7", + footer: true, + folder_id: "customer_satisfaction", + theme_id: 789 + }; + http:Response response = check surveyMonkey->/v3/surveys/["1267"].put(message, targetType = http:Response); } ``` + +## Complete sample + +The complete project is available in the [message sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/message) on GitHub. diff --git a/en/docs/guides/patterns/messaging-bridge.md b/en/docs/guides/patterns/messaging-bridge.md new file mode 100644 index 00000000000..2bef8377971 --- /dev/null +++ b/en/docs/guides/patterns/messaging-bridge.md @@ -0,0 +1,89 @@ +--- +title: Messaging Bridge +description: "Implement the Messaging Bridge pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Messaging Bridge + +Use a Messaging Bridge to connect two messaging systems so messages available on one system are also available on the other, letting clients of each system communicate without knowing about the other protocol. + +In WSO2 Integrator, a bridge is a service that listens on one protocol and forwards each message over a connection that speaks the other protocol, translating between the two message models. + +## Example: Bridging GraphQL clients to a REST API + +This example bridges GraphQL and REST. A GraphQL service accepts queries and mutations for projects, and each operation is forwarded to the Zoho Books REST API over an HTTP connection. GraphQL clients work with projects without knowing the backend is REST. + + + + +The design view shows the bridge itself: a GraphQL service on the front forwarding to the Zoho Books REST API connection on the back: + + + +1. Create a [GraphQL service](../../develop/integration-artifacts/service/graphql.md#creating-a-graphql-service) with a `project` query and a `createProject` mutation. +2. Add an HTTP client connection for the Zoho Books REST API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the query flow, call the Zoho Books `get` endpoint for the requested project and return the typed `Project`. +4. In the mutation flow, post the `ProjectRequest` to the Zoho Books `projects` endpoint and return the created `Project`. + +The `project` resolver forwards the GraphQL request to the Zoho Books REST endpoint and returns the typed result: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/graphql; +import ballerina/http; + +type ProjectRequest record {| + string projectName; + string description; + string customerName; +|}; + +type Project record {| + string projectID; + Task[] tasks; + string projectName; + string description; + string customerName; +|}; + +type Task record {| + string taskID; + string description; +|}; + +final http:Client zoho = check new ("http://zohoapis.com.balmock.io"); +// docs-fold-end + +listener graphql:Listener graphqlListener = new (listenTo = 8080); + +service /api/v1 on graphqlListener { + resource function get project(string organizationID, string projectID) returns Project|error { + Project result = check zoho->/books/v3/projects/[projectID].get(organization_id = organizationID); + return result; + } + + remote function createProject(string organizationID, ProjectRequest projectRequest) returns Project|error { + Project result = check zoho->/books/v3/projects.post(projectRequest, organization_id = organizationID); + return result; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [messaging bridge sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/messaging_bridge) on GitHub. diff --git a/en/docs/guides/patterns/normalizer.md b/en/docs/guides/patterns/normalizer.md new file mode 100644 index 00000000000..08119012fa0 --- /dev/null +++ b/en/docs/guides/patterns/normalizer.md @@ -0,0 +1,87 @@ +--- +title: Normalizer +description: "Implement the Normalizer pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Normalizer + +Use a Normalizer to process messages that are semantically equivalent but arrive in different formats. The normalizer routes each format to the right translator so the receiver always gets one common format. + +In WSO2 Integrator, the normalizer combines format detection (a union-typed payload and a type check) with a translator function that builds the common message format from whichever input arrived. + +## Example: Normalizing JSON and XML support tickets + +This example accepts support tickets as either JSON or XML. The service detects the incoming format, extracts the subject and comment from the matching structure, and calls the `normalize` function to build the single ticket format that the Zendesk API expects. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts a `json|xml` payload. +2. Add an HTTP client connection for the Zendesk API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) that checks whether the payload is JSON. +4. In each branch, extract the subject and comment from the format at hand and call the `normalize` function to build the common ticket structure. +5. Post the normalized ticket to the Zendesk tickets endpoint and return the ticket URL. + +The flow branches on the incoming format (JSON or XML), and both branches call the same `normalize` function to produce one common ticket format: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type ZendeskResponse record { + record {| + string url; + int id; + string subject; + |} ticket; +}; + +final http:Client zendeskClient = check new ("http://api.zendesk.com.balmock.io"); +// docs-fold-end + +function normalize(string subject, string comment) returns json { + return { + ticket: { + subject, + comment: { + body: comment + } + } + }; +} + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post ticket(@http:Payload json|xml request) returns string|error { + if request is json { + json normalizedRequest = normalize(check request.subject, check request.comment); + ZendeskResponse zendeskResponse = check zendeskClient->/api/v2/tickets.post(normalizedRequest); + return zendeskResponse.ticket.url; + } else { + json normalizedRequest = normalize((request/).data(), (request/).data()); + ZendeskResponse zendeskResponse = check zendeskClient->/api/v2/tickets.post(normalizedRequest); + return zendeskResponse.ticket.url; + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [normalizer sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/normalizer) on GitHub. diff --git a/en/docs/guides/patterns/overview.md b/en/docs/guides/patterns/overview.md new file mode 100644 index 00000000000..75f1589e6fd --- /dev/null +++ b/en/docs/guides/patterns/overview.md @@ -0,0 +1,88 @@ +--- +title: Enterprise Integration Patterns Overview +sidebar_label: Overview +description: "Implement Enterprise Integration Patterns with WSO2 Integrator." +--- + +# Enterprise Integration Patterns Overview + +Enterprise Integration Patterns (EIPs) are the accepted solutions to recurring problems in enterprise integration. They give integration architects a common language for describing how applications exchange messages, and a proven template for solving each class of problem. + +This section demonstrates how to implement the most widely used patterns with WSO2 Integrator. Each pattern page describes the problem the pattern solves, walks through a real-world example, and provides the complete Ballerina source, which you can open and edit visually in the WSO2 Integrator design view. The complete projects are available in the [WSO2 integration samples repository](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern). + +:::tip How to read these guides +Every pattern page shows the same integration two ways. The **Visual Designer** tab walks through building the pattern in WSO2 Integrator and includes a flow diagram that traces a message's path through the integration, node by node, so you can see the pattern's logic at a glance. The **Ballerina Code** tab shows the equivalent source. The two stay in sync: editing the visual flow updates the code, and editing the code updates the flow. +::: + +## Messaging Systems + +The basic building blocks of a messaging solution: how messages are structured, processed, routed, transformed, and consumed. + +| Pattern | Description | +|---------|-------------| +| [Message](message.md) | Exchange a piece of information between two applications connected by a message channel. | +| [Pipes and Filters](pipes-and-filters.md) | Perform complex processing on a message as a series of independent processing steps. | +| [Message Router](message-router.md) | Pass messages to different processing steps depending on a set of conditions. | +| [Message Translator](message-translator.md) | Let systems that use different data formats communicate with each other using messaging. | +| [Message Endpoint](message-endpoint.md) | Connect an application to a messaging channel so it can send and receive messages. | + +## Messaging Channels + +How messages move from sender to receiver, and how applications that were not built for messaging are connected to a channel. + +| Pattern | Description | +|---------|-------------| +| [Point-to-Point Channel](point-to-point-channel.md) | Ensure that exactly one receiver consumes a given message. | +| [Channel Adapter](channel-adapter.md) | Connect an application to the messaging system so it can send and receive messages. | +| [Messaging Bridge](messaging-bridge.md) | Connect two messaging systems so that messages available on one are also available on the other. | + +## Message Construction + +The intent, form, and content of the messages that travel through the system. + +| Pattern | Description | +|---------|-------------| +| [Command Message](command-message.md) | Use messaging to invoke a procedure in another application. | +| [Document Message](document-message.md) | Use messaging to transfer data between applications. | +| [Event Message](event-message.md) | Use messaging to transmit events from one application to another. | +| [Message Sequence](message-sequence.md) | Transmit an arbitrarily large amount of data as a sequence of smaller messages. | +| [Format Indicator](format-indicator.md) | Design a message's data format to allow for future changes. | + +## Message Routing + +How a message finds its way from the sender to the correct receiver or receivers, possibly through multiple processing steps. + +| Pattern | Description | +|---------|-------------| +| [Content-Based Router](content-based-router.md) | Route each message to the correct recipient based on the message content. | +| [Message Filter](message-filter.md) | Avoid receiving uninteresting messages by discarding the ones that do not match a condition. | +| [Splitter](splitter.md) | Break a message containing multiple elements into a series of individual messages. | +| [Aggregator](aggregator.md) | Combine the results of individual but related messages so they can be processed as a whole. | +| [Routing Slip](routing-slip.md) | Route a message through a series of steps when the sequence is not known at design time and varies per message. | +| [Process Manager](process-manager.md) | Route a message through multiple processing steps that may not be sequential or known at design time. | + +## Message Transformation + +How the content of a message is changed so the receiver gets the data it needs in the shape it expects. These transformations can be built without writing conversion code using WSO2 Integrator's visual [Data Mapper](../../develop/integration-artifacts/supporting/data-mapper/data-mapper.md). + +| Pattern | Description | +|---------|-------------| +| [Content Enricher](content-enricher.md) | Communicate with another system when the message originator does not have all the required data items. | +| [Content Filter](content-filter.md) | Simplify dealing with a large message when you are interested in only a few data items. | +| [Normalizer](normalizer.md) | Process messages that are semantically equivalent but arrive in different formats. | + +## Messaging Endpoints + +How an application produces and consumes messages correctly. + +| Pattern | Description | +|---------|-------------| +| [Idempotent Receiver](idempotent-receiver.md) | Deal with duplicate messages safely at the receiver. | + +## System Management + +How a messaging system is monitored and managed in production. + +| Pattern | Description | +|---------|-------------| +| [Message Store](message-store.md) | Report against message information without disturbing the loosely coupled nature of the system. | diff --git a/en/docs/guides/patterns/pipes-and-filters.md b/en/docs/guides/patterns/pipes-and-filters.md new file mode 100644 index 00000000000..20b69422faa --- /dev/null +++ b/en/docs/guides/patterns/pipes-and-filters.md @@ -0,0 +1,78 @@ +--- +title: Pipes and Filters +description: "Implement the Pipes and Filters pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Pipes and Filters + +Use Pipes and Filters to perform complex processing on a message as a sequence of independent processing steps (filters) connected by channels (pipes), so each step can be developed, tested, and reused on its own. + +In WSO2 Integrator, a Ballerina query expression is a natural pipes-and-filters chain. Each clause (`let`, `where`, `limit`, `order by`, `select`) is an independent filter, and the query pipes the output of one clause into the next. + +## Example: Ranking top-performing employees + +This example exposes a service that returns the top-performing employees. The flow retrieves raw performance records from a Firebase datastore and pushes them through a chain of processing steps: compute a weighted performance score, filter out scores below the threshold, limit the result count, order by score, and project the final shape. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `get` resource that accepts the result count. +2. Add an HTTP client connection for the Firebase datastore. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, call the connection to retrieve the `EmployeePerformance[]` records. +4. Add a query that chains the processing steps: a `let` clause to compute the weighted performance score, a `where` clause to keep scores above `7.5`, `limit` and `order by` clauses, and a `select` clause that builds the `TopPerformer` result. +5. Return the query result from the resource. + +The flow retrieves the raw records and passes them through a query whose clauses act as successive filters: score, threshold, limit, order, and projection: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type EmployeePerformance record {| + string empId; + int productivity; + int customerSatisfaction; + int goalAchievement; +|}; + +type TopPerformer record {| + string empId; + float performance; +|}; + +final http:Client firebaseClient = check new ("http://api.employee.performance.firebase.com.balmock.io"); +// docs-fold-end + +listener http:Listener httpDefaultListener = http:getDefaultListener(); + +service /api/v1 on httpDefaultListener { + isolated resource function get employee/top\-performers(int count) returns TopPerformer[]|error { + EmployeePerformance[] employeePerformance = check firebaseClient->/performance\.json.get(); + return from var {empId, productivity, customerSatisfaction, goalAchievement} in employeePerformance + let float performance = productivity * 0.3 + customerSatisfaction * 0.1 + goalAchievement * 0.6 + where performance > 7.5 + limit count + order by performance descending + select {empId, performance}; + } +} +``` + + + + +## Complete sample + +The complete project is available in the [pipes and filters sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/pipes_and_filters) on GitHub. diff --git a/en/docs/guides/patterns/point-to-point-channel.md b/en/docs/guides/patterns/point-to-point-channel.md new file mode 100644 index 00000000000..c9a32a43e22 --- /dev/null +++ b/en/docs/guides/patterns/point-to-point-channel.md @@ -0,0 +1,71 @@ +--- +title: Point-to-Point Channel +description: "Implement the Point-to-Point Channel pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Point-to-Point Channel + +Use a Point-to-Point Channel when exactly one receiver should consume each message the sender puts on the channel. + +In WSO2 Integrator, a client connection to a single receiver is a point-to-point channel: each message sent over the connection is delivered to and consumed by that one receiver. + +## Example: Creating a product in a billing system + +This example sends a product creation message to the Zuora billing API over a dedicated client connection. Only the Zuora endpoint consumes the message, and the response confirms that the single receiver processed it. + + + + +The design view shows the channel: a single automation connected to one Zuora connection, so the message has exactly one sender and one receiver: + + + +1. Create an [automation](../../develop/integration-artifacts/automation.md#creating-an-automation) to run the flow. +2. Add an HTTP client connection that points to the Zuora API. This connection is the point-to-point channel. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, declare a variable holding the product details. +4. Add the HTTP `post` action on the connection to send the product to the single receiver and capture the `ProductCreationResponse`. + +The flow sends the product to the single Zuora receiver, so exactly one consumer handles the message: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type ProductCreationResponse record {| + boolean success; + string id; +|}; + +final http:Client zuora = check new ("http://rest.zuora.com.balmock.io"); +// docs-fold-end + +public function main() returns error? { + var product = { + "Description": "Cell phone service for call center operators", + "EffectiveEndDate": "2025-10-01", + "EffectiveStartDate": "2023-10-01", + "Name": "Cell Phone Service", + "SKU": "API-SKU09723199712" + }; + ProductCreationResponse productCreationResponse = check zuora->/v1/'object/product.post(product, targetType = ProductCreationResponse); +} +``` + + + + +## Complete sample + +The complete project is available in the [point-to-point channel sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/point_to_point_channel) on GitHub. diff --git a/en/docs/guides/patterns/polling-consumer.md b/en/docs/guides/patterns/polling-consumer.md deleted file mode 100644 index 0d20e5aebcf..00000000000 --- a/en/docs/guides/patterns/polling-consumer.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: Polling Consumer -description: "Implement the Polling Consumer pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Polling Consumer - -Use the Polling Consumer pattern when an integration must decide when it is ready to read from a channel instead of receiving pushed messages automatically. - -The pattern is implemented at the point where the flow controls the receive call. Use a loop when the integration must keep asking until a message or terminal state is available. Use a scheduled automation when each execution should perform one explicit pull operation. - -## Loop-driven polling - -Use loop-driven polling with [while loops](../../develop/understand-ide/editors/flow-diagram-editor/control.md#while) when the integration should keep checking a channel or endpoint while it controls the maximum attempts and wait interval. The receive or status-check call stays inside the loop, and the flow exits when it receives a message that is ready to process. - - - - -1. Create or open the [HTTP service resource](../../develop/integration-artifacts/service/http.md#creating-an-http-service) that starts the polling flow. -2. Add an HTTP client connection for the source that the flow must poll. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection) and the [HTTP client reference](../../connectors/catalog/built-in/http/action-reference.md#client). -3. Add [configurable variables](../../reference/config/configuration-management.md#configurable-variables) for values such as `maxAttempts` and `pollDelaySeconds`. -4. Add a [While node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#while) that runs while the attempt count is less than `maxAttempts`. -5. Inside the loop, add the HTTP client operation that asks for the current message or status. -6. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) that returns the message when it is ready. Otherwise, wait for the configured delay and continue the loop. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; -import ballerina/lang.runtime; - -configurable int maxAttempts = 10; -configurable decimal pollDelaySeconds = 5.0d; -configurable string statusServiceUrl = ?; - -type StatusMessage record {| - string id; - string status; - json payload; -|}; - -final http:Client statusClient = check new (statusServiceUrl); -// docs-fold-end - -service /messages on new http:Listener(8080) { - resource function get [string messageId]() returns StatusMessage|error { - int attempt = 0; - - while attempt < maxAttempts { - StatusMessage message = check statusClient->/[messageId](); - if message.status == "READY" { - return message; - } - - attempt += 1; - runtime:sleep(pollDelaySeconds); - } - - return error("Message was not ready before the polling limit"); - } -} -``` - - - - -## Scheduled broker polling - -Use scheduled broker polling when each automation run should pull at most one message from a broker and then stop. This keeps the schedule outside the receive logic while the flow still controls when it asks the broker for the next message. For JMS-backed channels, use the [JMS Message Consumer actions](../../connectors/catalog/messaging/java.jms/actions.md#message-consumer) with `receive` or `receiveNoWait`. - - - - -1. Create a [scheduled automation](../../develop/integration-artifacts/automation.md#creating-an-automation) for the polling interval. -2. Add the `java.jms` **JMS MessageConsumer** connection and bind the broker settings to configurable variables. See the [JMS consumer example](../../connectors/catalog/messaging/java.jms/example.md#adding-the-javajms-connector). -3. Add the **Receive** operation from the JMS consumer connection and set the timeout value for the polling window. -4. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) that checks whether the received value is a message. -5. Add the processing steps inside the branch that received a message. -6. Acknowledge the message only after the processing steps finish successfully. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/log; -import ballerinax/activemq.driver as _; -import ballerinax/java.jms as jms; - -configurable string providerUrl = ?; -configurable string queueName = "Orders"; -configurable int pollTimeoutMillis = 3000; - -function createConsumer() returns jms:MessageConsumer|error { - jms:Connection connection = check new ( - initialContextFactory = "org.apache.activemq.jndi.ActiveMQInitialContextFactory", - providerUrl = providerUrl - ); - jms:Session session = check connection->createSession(jms:CLIENT_ACKNOWLEDGE); - return session.createConsumer(destination = { - 'type: jms:QUEUE, - name: queueName - }); -} - -function processMessage(string payload) returns error? { - log:printInfo("Processing message", payload = payload); -} -// docs-fold-end - -public function main() returns error? { - jms:MessageConsumer consumer = check createConsumer(); - jms:Message? message = check consumer->receive(pollTimeoutMillis); - - if message is jms:TextMessage { - check processMessage(message.content); - check consumer->acknowledge(message); - } else if message is jms:Message { - log:printInfo("Received a non-text JMS message"); - check consumer->acknowledge(message); - } else { - log:printInfo("No message was available in this polling window"); - } -} -``` - - - diff --git a/en/docs/guides/patterns/process-manager.md b/en/docs/guides/patterns/process-manager.md new file mode 100644 index 00000000000..e40fff79199 --- /dev/null +++ b/en/docs/guides/patterns/process-manager.md @@ -0,0 +1,195 @@ +--- +title: Process Manager +description: "Implement the Process Manager pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Process Manager + +Use a Process Manager to route a message through multiple processing steps when the required steps are not fixed at design time and may not be sequential. The process manager maintains the state of the sequence and determines the next step based on intermediate results. + +In WSO2 Integrator, the process manager is the central flow that calls each processing step, holds the intermediate results, and decides the next step with control nodes. Steps that do not affect the outcome can run asynchronously with `start`. + +## Example: Orchestrating order fulfillment + +This example orchestrates a book order across multiple systems. The process manager first creates the order in Shopify, then chooses the shipping step based on the result (FedEx for United States addresses, DHL Express otherwise), and finally triggers the confirmation email asynchronously through SendGrid. + + + + +The design view shows the order service fanning out to the four systems that the process manager coordinates (Shopify, FedEx, DHL Express, and SendGrid): + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `OrderRequest` payload. +2. Add HTTP client connections for Shopify, FedEx, DHL Express, and SendGrid. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. In the flow, post the order to Shopify and capture the `OrderResponse`. This intermediate result drives the next step. +4. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) on the shipping country: create a FedEx shipment for United States orders, and a DHL shipment otherwise, capturing the tracking number from either branch. +5. Start the `sendConfirmationMail` step asynchronously so the customer notification does not block the process. + +The flow creates the order in Shopify, chooses FedEx or DHL by destination country, and triggers the confirmation email asynchronously: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type OrderRequest record {| + string email; + Address address; + OrderItemRequest[] orderItems; +|}; + +type OrderResponse record {| + string email; + string currency; + float total; + Address address; + OrderItemResponse[] orderItems; + string trackingNumber; +|}; + +type Address record {| + string fullName; + string address1; + string phone; + string city; + string country; +|}; + +type OrderItemRequest record { + string itemName; + int quantity; +}; + +type OrderItemResponse record {| + string itemName; + int quantity; + float price; + string currencyCode; +|}; + +type ShipmentRequest record {| + float amount; + string currency; + string personName; + string email; + DHLAddress|FedexAddress address; +|}; + +type FedexAddress record {| + string address1; + string city; + string country; + string phoneNumber; +|}; + +type DHLAddress record {| + string name; + string address1; + string city; + string country; + string phoneNumber; +|}; + +type FedexResponse record {| + string transactionId; + string trackingNumber; +|}; + +type DHLResponse record {| + string trackingNumber; +|}; + +final http:Client shopify = check new ("http://BlackwellsBooks.myshopify.com.balmock.io"); +final http:Client dhlExpress = check new ("http://express.api.dhl.com.balmock.io"); +final http:Client fedEx = check new ("http://api.fedex.com.balmock.io"); +final http:Client sendgrid = check new ("http://api.sendgrid.com.balmock.io"); +// docs-fold-end + +// docs-fold-start: Processing step functions +function createFedexShipment(OrderResponse response) returns FedexResponse|error { + ShipmentRequest fedexReq = { + amount: response.total, + currency: response.currency, + personName: response.address.fullName, + email: response.email, + address: { + address1: response.address.address1, + city: response.address.city, + country: response.address.country, + phoneNumber: response.address.phone + } + }; + + FedexResponse targetType = check fedEx->/api/en\-us/catalog/ship/v1/shipments.post(fedexReq); + return targetType; +} + +function createDhlShipment(OrderResponse response) returns DHLResponse|error { + ShipmentRequest dhlReq = { + amount: response.total, + currency: response.currency, + personName: response.address.fullName, + email: response.email, + address: { + name: response.address.fullName, + address1: response.address.address1, + city: response.address.city, + country: response.address.country, + phoneNumber: response.address.phone + } + }; + + DHLResponse targetType = check dhlExpress->/mydhlapi/shipments.post(dhlReq); + return targetType; +} + +function sendConfirmationMail(string name, string email, string trackingNumber) returns error? { + string body = string `

Hello ${name}!

Your Order has been shipped. ` + + string `Track your order using ${trackingNumber}

`; + var mailReq = { + toInfo: email, + fromInfo: "orders@blackwell.com", + subject: "Order Confirmation", + content: body + }; + + json jsonResult = check sendgrid->/v3/mail/send.post(mailReq, targetType = json); +} +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post orders(OrderRequest orderReq) returns error? { + OrderResponse response = check shopify->/admin/api/orders\.json.post(orderReq); + string trackingNumber; + if response.address.country == "United States" { + FedexResponse fedexResp = check createFedexShipment(response); + trackingNumber = fedexResp.trackingNumber; + } else { + DHLResponse dhlResp = check createDhlShipment(response); + trackingNumber = dhlResp.trackingNumber; + } + future futureResult = start sendConfirmationMail(response.address.fullName, response.email, trackingNumber); + } +} +``` + +
+
+ +## Complete sample + +The complete project is available in the [process manager sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/process_manager) on GitHub. diff --git a/en/docs/guides/patterns/routing-slip.md b/en/docs/guides/patterns/routing-slip.md new file mode 100644 index 00000000000..3fc026e28e5 --- /dev/null +++ b/en/docs/guides/patterns/routing-slip.md @@ -0,0 +1,133 @@ +--- +title: Routing Slip +description: "Implement the Routing Slip pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Routing Slip + +Use a Routing Slip to route a message consecutively through a series of processing steps when the sequence of steps is not known at design time and may vary for each message. The slip is attached to the message, and each component processes its step and passes the message on. + +In WSO2 Integrator, the slip is computed per message and attached to it as a field. The flow inspects the slip and forwards the message to the steps it lists, so each message follows its own route. + +## Example: Applying loyalty point steps to a payment + +This example processes retail payments where the applicable discount steps differ per customer. For each payment, the flow looks up which point programs the customer belongs to (store loyalty points and mobile points) and builds a routing slip listing only those steps. The message is then sent through the point-handling steps named on its slip before the final checkout computes the redeemed amount. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `PaymentRequest` payload. +2. In the flow, call the `lookupMessageSlip` function: it checks the loyalty and mobile point memberships for the customer and returns the routing slip for this message. +3. Attach the slip to the message by building a `Message` record from the request and the slip. +4. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) that forwards the message to the point-handler service when the slip is not empty. +5. Call the `checkout` function to compute the redeemed amount and return the `PaymentStatus`. + +The flow computes the routing slip for each payment, then sends the message through only the point-handling steps the slip lists before checkout: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; + +type PaymentRequest record {| + string mobileNumber; + string customerName; + float totalAmount; + string storeCode; + record {}[] items; +|}; + +type PaymentStatus record {| + string status; + record { + float totalPoints; + float redeemedAmount; + float totalAmount; + } details; +|}; + +type Message record {| + string mobileNumber; + string customerName; + float totalAmount; + record { + }[] items; + string storeCode; + string[] routingSlip = []; +|}; + +type Points record { + float loyaltyPoints = 0.0; + float mobilePoints = 0.0; +}; + +function checkout(Message message, Points points) returns PaymentStatus { + float totalPoints = points.loyaltyPoints + points.mobilePoints; + return { + status: "SUCCESS", + details: { + totalPoints: totalPoints, + redeemedAmount: totalPoints * 50, + totalAmount: message.totalAmount - (totalPoints * 50) + } + }; +} + +function isRegisteredToPointsService(string mobileNumber) returns boolean|error { + http:Client openLoyalty = check new ("http://mob.points.hub.com.balmock.io"); + anydata|error memberCheck = openLoyalty->/api/[mobileNumber]/member/'check/get.get(); + return memberCheck is error ? false : true; +} +// docs-fold-end + +function lookupMessageSlip(PaymentRequest request) returns string[]|error { + final http:Client openLoyalty = check new ("http://openloyalty.com.balmock.io"); + anydata|error customer = openLoyalty->/api/[request.storeCode]/member/'check/get.get(); + string[] routingSlip = []; + if customer is anydata { + () var1 = routingSlip.push("CustomerLoyaltyPoints"); + } + if check isRegisteredToPointsService(request.mobileNumber) { + () var1 = routingSlip.push("MobilePoints"); + } + return routingSlip; +} + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post payments(PaymentRequest request) returns PaymentStatus|error { + string[] routingSlip = check lookupMessageSlip(request); + Message message = {...request, routingSlip: routingSlip}; + Points points = {}; + if message.routingSlip.length() > 0 { + final http:Client pointHandler = check new ("http://localhost:8081/loyaltyPoints"); + json payload = { + storeCode: message.storeCode, + mobileNumber: message.mobileNumber, + routingSlip: message.routingSlip + }; + points = check pointHandler->/points.post(payload); + } + return checkout(message, points); + } +} +``` + + + + +## Complete sample + +The complete project is available in the [routing slip sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/routing_slip) on GitHub. diff --git a/en/docs/guides/patterns/selective-consumer.md b/en/docs/guides/patterns/selective-consumer.md deleted file mode 100644 index 74a0cf40afc..00000000000 --- a/en/docs/guides/patterns/selective-consumer.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Selective Consumer -description: "Implement the Selective Consumer pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Selective Consumer - -Use the Selective Consumer pattern when a service reads from a shared channel but should receive or process only the messages that match its criteria. - -The pattern is implemented at the consumer boundary or immediately inside the consumer flow. Use broker-side selection when the channel supports selectors based on message metadata. Use flow-level selection when the decision depends on payload content or rules that must run after delivery. - -## Broker-side selection - -Use broker-side selection when the broker can evaluate the criteria before the message reaches the service. For JMS-backed channels, configure a [JMS listener service](../../connectors/catalog/messaging/java.jms/triggers.md#service) with `messageSelector` so the service receives only messages whose headers or properties match the selector expression. - - - - -1. Create the JMS-backed event service with the [JMS listener](../../connectors/catalog/messaging/java.jms/triggers.md#listener). -2. Configure the listener connection with the broker endpoint and credentials through [configurable variables](../../reference/config/configuration-management.md#configurable-variables). -3. Set the service queue or topic in `@jms:ServiceConfig`. -4. Set `messageSelector` to the selector expression, such as `eventType = 'OrderCreated' AND priority = 'high'`. -5. Add processing steps in the `onMessage` flow. The broker delivers only messages that match the selector. -6. Ensure producers set the message properties used by the selector before publishing to the shared channel. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/log; -import ballerinax/java.jms; - -configurable string providerUrl = ?; - -listener jms:Listener orderListener = check new ({ - initialContextFactory: "org.apache.activemq.jndi.ActiveMQInitialContextFactory", - providerUrl: providerUrl -}); - -function processPriorityOrder(jms:Message message) returns error? { - log:printInfo("Processing selected order", id = message.messageId); -} -// docs-fold-end - -@jms:ServiceConfig { - queueName: "orders", - messageSelector: "eventType = 'OrderCreated' AND priority = 'high'" -} -service "priority-order-consumer" on orderListener { - remote function onMessage(jms:Message message) returns error? { - check processPriorityOrder(message); - } -} -``` - - - - -## Flow-level selection - -Use flow-level selection with [if/else statements](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) when the consumer must inspect the delivered payload, call another system, or apply rules that cannot be expressed as a broker selector. The service still reads from the shared channel, but the accepted branch contains the processing logic and the unmatched branch is ignored or handled separately. - - - - -1. Create or open the consumer service that receives messages from the shared channel. -2. Open the message handler flow and [add a step](../../develop/understand-ide/editors/flow-diagram-editor/flow-diagram-editor.md#anatomy-of-the-editor). -3. Add an [If node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#if) at the point where the flow has the payload fields needed for selection. -4. Set the condition to the consumer criteria, such as `order.priority == "high" && order.region == "west"`. -5. Add the processing steps inside the **True** branch. -6. Leave the **False** branch empty when unmatched messages should be ignored, or add separate handling for rejected messages. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/log; - -type OrderEvent record {| - string orderId; - string priority; - string region; -|}; - -function processWestPriorityOrder(OrderEvent order) returns error? { - log:printInfo("Processing selected order", orderId = order.orderId); -} -// docs-fold-end - -function consumeOrder(OrderEvent order) returns error? { - if order.priority == "high" && order.region == "west" { - check processWestPriorityOrder(order); - } -} -``` - - - diff --git a/en/docs/guides/patterns/service-activator.md b/en/docs/guides/patterns/service-activator.md deleted file mode 100644 index 6d229d610aa..00000000000 --- a/en/docs/guides/patterns/service-activator.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Service Activator -description: "Implement the Service Activator pattern with WSO2 Integrator." ---- - -import TabItem from '@theme/TabItem'; -import { - EipReferenceLink, - PatternImplementationTabs, -} from '@site/src/utils/eipPatternComponents'; - -# Service Activator - -Use a Service Activator to expose the same application operation through a message channel and a direct service interface. - -The pattern is implemented by keeping the business operation in a reusable function and placing thin protocol-specific adapters around it. A service resource handles non-messaging callers, while a broker listener activates the same function when a message arrives. - -## Request-reply service activation - -Use request-reply service activation when a message sender expects the activated service to return a result. Define typed request and response records, implement the operation as a reusable function, and call that function from both the direct resource function and the broker request handler. - - - - -1. Define the request and response records in [Types](../../develop/integration-artifacts/supporting/types.md). -2. Add the reusable operation as a [Function](../../develop/integration-artifacts/supporting/functions.md) and select **Make visible across the project** when the function must be called from multiple artifacts. -3. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) for non-messaging callers. -4. Add a resource function with the typed request payload and add a **Call Function** step that invokes the reusable operation. -5. Add a [RabbitMQ event integration](../../develop/integration-artifacts/event/rabbitmq.md#creating-a-rabbitmq-service) for message-based callers. -6. Configure the RabbitMQ queue and listener values with [configurable variables](../../reference/config/configuration-management.md#configurable-variables). -7. Add the `onRequest` handler from [RabbitMQ event handlers](../../develop/integration-artifacts/event/rabbitmq.md#event-handlers), define the expected message content, add a **Call Function** step, and return the function result. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerina/http; -import ballerinax/rabbitmq; - -configurable string rabbitmqHost = "localhost"; -configurable int rabbitmqPort = 5672; - -type ActivationRequest record {| - string requestId; - string customerId; - decimal amount; -|}; - -type ActivationResult record {| - string requestId; - string status; -|}; - -listener rabbitmq:Listener activatorListener = new (rabbitmqHost, rabbitmqPort); -// docs-fold-end - -function activateService(ActivationRequest request) returns ActivationResult|error { - return { - requestId: request.requestId, - status: "accepted" - }; -} - -service /requests on new http:Listener(8080) { - resource function post activate(ActivationRequest request) - returns ActivationResult|error { - return activateService(request); - } -} - -@rabbitmq:ServiceConfig { - queueName: "service-requests" -} -service rabbitmq:Service on activatorListener { - remote function onRequest(rabbitmq:AnydataMessage message) - returns ActivationResult|error { - ActivationRequest request = check message.content.ensureType(); - return activateService(request); - } -} -``` - - - - -## One-way command activation - -Use one-way command activation when the message channel only needs to trigger the operation and does not need a service result in the reply path. The broker handler validates the message content, calls the shared function, and lets the handler return successfully after the command is accepted. - - - - -1. Reuse the request and response records from [Types](../../develop/integration-artifacts/supporting/types.md). -2. Reuse the same [Function](../../develop/integration-artifacts/supporting/functions.md) that contains the application operation. -3. Add a [RabbitMQ event integration](../../develop/integration-artifacts/event/rabbitmq.md#creating-a-rabbitmq-service) for the command queue. -4. Configure the listener, queue, and authentication fields with [configurable variables](../../reference/config/configuration-management.md#configurable-variables). -5. Add the `onMessage` handler from [RabbitMQ event handlers](../../develop/integration-artifacts/event/rabbitmq.md#event-handlers) and define the expected message content. -6. In the handler flow, add a **Call Function** step for the shared operation and add any flow-level error handling required by the command contract. - - - - -```ballerina -// docs-fold-start: Supporting definitions -import ballerinax/rabbitmq; - -configurable string rabbitmqHost = "localhost"; -configurable int rabbitmqPort = 5672; - -type ActivationRequest record {| - string requestId; - string customerId; - decimal amount; -|}; - -type ActivationResult record {| - string requestId; - string status; -|}; - -listener rabbitmq:Listener commandListener = new (rabbitmqHost, rabbitmqPort); - -function activateService(ActivationRequest request) returns ActivationResult|error { - return { - requestId: request.requestId, - status: "accepted" - }; -} -// docs-fold-end - -@rabbitmq:ServiceConfig { - queueName: "service-commands" -} -service rabbitmq:Service on commandListener { - remote function onMessage(rabbitmq:AnydataMessage message) returns error? { - ActivationRequest request = check message.content.ensureType(); - _ = check activateService(request); - } -} -``` - - - diff --git a/en/docs/guides/patterns/splitter.md b/en/docs/guides/patterns/splitter.md new file mode 100644 index 00000000000..c076c7f5519 --- /dev/null +++ b/en/docs/guides/patterns/splitter.md @@ -0,0 +1,94 @@ +--- +title: Splitter +description: "Implement the Splitter pattern with WSO2 Integrator." +--- + +import TabItem from '@theme/TabItem'; +import { + EipReferenceLink, + PatternImage, + PatternImplementationTabs, +} from '@site/src/utils/eipPatternComponents'; + +# Splitter + +Use a Splitter to break a message that contains multiple elements into a series of individual messages, so each element can be processed on its own. + +In WSO2 Integrator, the splitter is a [Foreach node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#foreach) over the repeating elements of the message. Each iteration produces and processes one individual message. + +## Example: Sending per-attendee event reminders + +This example receives a single reminder request that contains multiple events, each with multiple attendees. The flow splits the composite message twice (once per event and once per attendee) and sends each attendee an individual SMS reminder through the Twilio API. + + + + +1. Create an [HTTP service](../../develop/integration-artifacts/service/http.md#creating-an-http-service) with a `post` resource that accepts the `ReminderRequest` payload. +2. Add an HTTP client connection for the Twilio API. See [adding a connection](../../develop/integration-artifacts/supporting/connections.md#adding-a-connection). +3. Add a [Foreach node](../../develop/understand-ide/editors/flow-diagram-editor/control.md#foreach) over `request.events`, and a nested **Foreach** over `event.attendees`. +4. Inside the inner loop, call the `sendReminder` function, which builds the personalized message for one attendee and posts it to the Twilio messages endpoint. + +Nested **Foreach** nodes split the request into one message per event and then per attendee, sending each attendee an individual reminder: + + + +The [type diagram](../../develop/understand-ide/editors/type-diagram-editor.md) shows the composite message the splitter breaks down: a `ReminderRequest` holds many `Event` records, and each `Event` holds many `Attendee` records: + + + + + + +```ballerina +// docs-fold-start: Supporting definitions +import ballerina/http; +import ballerina/mime; +import ballerina/url; + +type ReminderRequest record { + string date; + Event[] events; +}; + +type Event record {| + string eventName; + Attendee[] attendees; +|}; + +type Attendee record {| + string name; + string number; +|}; + +final http:Client twilio = check new ("http://api.twilio.com.balmock.io"); + +function sendReminder(Attendee attendee, string eventName, string date) returns error? { + string body = string `Hi ${attendee.name}, looking forward to meet you at the ${eventName} on ${date}`; + string payload = "From=" + check url:encode("+15005550006", "utf-8") + + "&To=" + check url:encode(attendee.number, "utf-8") + + "&Body=" + check url:encode(body, "utf-8"); + http:Request twilioReq = new http:Request(); + () var1 = twilioReq.setTextPayload(payload, contentType = mime:APPLICATION_FORM_URLENCODED); + http:Response response = check twilio->/["2010-04-01"]/Accounts/["VAC1829a53d52f41b4b2b1cc003c0026aa8"]/Messages\.json.post(twilioReq, targetType = http:Response); +} +// docs-fold-end + +listener http:Listener httpListener = new (port = 8080); + +service /api/v1 on httpListener { + resource function post reminders(ReminderRequest request) returns error? { + foreach Event event in request.events { + foreach Attendee attendee in event.attendees { + check sendReminder(attendee, event.eventName, request.date); + } + } + } +} +``` + + + + +## Complete sample + +The complete project is available in the [splitter sample](https://github.com/wso2/integration-samples/tree/main/integrator-default-profile/enterprise-integration-pattern/splitter) on GitHub. diff --git a/en/sidebars.ts b/en/sidebars.ts index 50c404c7e90..5c25f9e7560 100644 --- a/en/sidebars.ts +++ b/en/sidebars.ts @@ -1871,16 +1871,70 @@ const sidebars: SidebarsConfig = { { type: 'category', label: 'Enterprise Integration Patterns', + link: { type: 'doc', id: 'guides/patterns/overview' }, items: [ - 'guides/patterns/message', - 'guides/patterns/message-filter', - 'guides/patterns/content-based-routing', - 'guides/patterns/selective-consumer', - 'guides/patterns/polling-consumer', - 'guides/patterns/channel-adapter', - 'guides/patterns/message-dispatcher', - 'guides/patterns/service-activator', - 'guides/patterns/message-mapper', + { + type: 'category', + label: 'Messaging Systems', + items: [ + 'guides/patterns/message', + 'guides/patterns/pipes-and-filters', + 'guides/patterns/message-router', + 'guides/patterns/message-translator', + 'guides/patterns/message-endpoint', + ], + }, + { + type: 'category', + label: 'Messaging Channels', + items: [ + 'guides/patterns/point-to-point-channel', + 'guides/patterns/channel-adapter', + 'guides/patterns/messaging-bridge', + ], + }, + { + type: 'category', + label: 'Message Construction', + items: [ + 'guides/patterns/command-message', + 'guides/patterns/document-message', + 'guides/patterns/event-message', + 'guides/patterns/message-sequence', + 'guides/patterns/format-indicator', + ], + }, + { + type: 'category', + label: 'Message Routing', + items: [ + 'guides/patterns/content-based-router', + 'guides/patterns/message-filter', + 'guides/patterns/splitter', + 'guides/patterns/aggregator', + 'guides/patterns/routing-slip', + 'guides/patterns/process-manager', + ], + }, + { + type: 'category', + label: 'Message Transformation', + items: [ + 'guides/patterns/content-enricher', + 'guides/patterns/content-filter', + 'guides/patterns/normalizer', + ], + }, + { + type: 'category', + label: 'Messaging Endpoints', + items: ['guides/patterns/idempotent-receiver'], + }, + { + type: 'category', + label: 'System Management', + items: ['guides/patterns/message-store'], + }, ], }, // Migration Guides diff --git a/en/src/utils/eipPatternComponents.tsx b/en/src/utils/eipPatternComponents.tsx index e3b372a1c21..75c497ae081 100644 --- a/en/src/utils/eipPatternComponents.tsx +++ b/en/src/utils/eipPatternComponents.tsx @@ -1,4 +1,4 @@ -import React, { type ComponentProps } from 'react'; +import React, { use, type ComponentProps } from 'react'; import Tabs from '@theme/Tabs'; import useBaseUrl from '@docusaurus/useBaseUrl'; diff --git a/en/static/img/eip-patterns/aggregator_flow.png b/en/static/img/eip-patterns/aggregator_flow.png new file mode 100644 index 00000000000..70f2978df5c Binary files /dev/null and b/en/static/img/eip-patterns/aggregator_flow.png differ diff --git a/en/static/img/eip-patterns/channel_adapter_design.png b/en/static/img/eip-patterns/channel_adapter_design.png new file mode 100644 index 00000000000..3127b21272a Binary files /dev/null and b/en/static/img/eip-patterns/channel_adapter_design.png differ diff --git a/en/static/img/eip-patterns/channel_adapter_flow.png b/en/static/img/eip-patterns/channel_adapter_flow.png new file mode 100644 index 00000000000..6eebdac68d3 Binary files /dev/null and b/en/static/img/eip-patterns/channel_adapter_flow.png differ diff --git a/en/static/img/eip-patterns/command_message_flow.png b/en/static/img/eip-patterns/command_message_flow.png new file mode 100644 index 00000000000..0654b9a2d7c Binary files /dev/null and b/en/static/img/eip-patterns/command_message_flow.png differ diff --git a/en/static/img/eip-patterns/command_message_types.png b/en/static/img/eip-patterns/command_message_types.png new file mode 100644 index 00000000000..033458f2c34 Binary files /dev/null and b/en/static/img/eip-patterns/command_message_types.png differ diff --git a/en/static/img/eip-patterns/content_based_router_flow.png b/en/static/img/eip-patterns/content_based_router_flow.png new file mode 100644 index 00000000000..33d0868919b Binary files /dev/null and b/en/static/img/eip-patterns/content_based_router_flow.png differ diff --git a/en/static/img/eip-patterns/content_enricher_design.png b/en/static/img/eip-patterns/content_enricher_design.png new file mode 100644 index 00000000000..5365716fa0a Binary files /dev/null and b/en/static/img/eip-patterns/content_enricher_design.png differ diff --git a/en/static/img/eip-patterns/content_enricher_flow.png b/en/static/img/eip-patterns/content_enricher_flow.png new file mode 100644 index 00000000000..2f3e5136af7 Binary files /dev/null and b/en/static/img/eip-patterns/content_enricher_flow.png differ diff --git a/en/static/img/eip-patterns/content_filter_datamapper.png b/en/static/img/eip-patterns/content_filter_datamapper.png new file mode 100644 index 00000000000..574c7e86744 Binary files /dev/null and b/en/static/img/eip-patterns/content_filter_datamapper.png differ diff --git a/en/static/img/eip-patterns/content_filter_flow.png b/en/static/img/eip-patterns/content_filter_flow.png new file mode 100644 index 00000000000..e85a016622c Binary files /dev/null and b/en/static/img/eip-patterns/content_filter_flow.png differ diff --git a/en/static/img/eip-patterns/document_message_flow.png b/en/static/img/eip-patterns/document_message_flow.png new file mode 100644 index 00000000000..46be3bcfcf8 Binary files /dev/null and b/en/static/img/eip-patterns/document_message_flow.png differ diff --git a/en/static/img/eip-patterns/event_message_flow.png b/en/static/img/eip-patterns/event_message_flow.png new file mode 100644 index 00000000000..24a8f300c96 Binary files /dev/null and b/en/static/img/eip-patterns/event_message_flow.png differ diff --git a/en/static/img/eip-patterns/format_indicator_datamapper.png b/en/static/img/eip-patterns/format_indicator_datamapper.png new file mode 100644 index 00000000000..d30af85fd98 Binary files /dev/null and b/en/static/img/eip-patterns/format_indicator_datamapper.png differ diff --git a/en/static/img/eip-patterns/format_indicator_flow.png b/en/static/img/eip-patterns/format_indicator_flow.png new file mode 100644 index 00000000000..51e841d80a4 Binary files /dev/null and b/en/static/img/eip-patterns/format_indicator_flow.png differ diff --git a/en/static/img/eip-patterns/format_indicator_types.png b/en/static/img/eip-patterns/format_indicator_types.png new file mode 100644 index 00000000000..bc929a6165c Binary files /dev/null and b/en/static/img/eip-patterns/format_indicator_types.png differ diff --git a/en/static/img/eip-patterns/idempotent_receiver_flow.png b/en/static/img/eip-patterns/idempotent_receiver_flow.png new file mode 100644 index 00000000000..044849b5bac Binary files /dev/null and b/en/static/img/eip-patterns/idempotent_receiver_flow.png differ diff --git a/en/static/img/eip-patterns/message_endpoint_flow.png b/en/static/img/eip-patterns/message_endpoint_flow.png new file mode 100644 index 00000000000..94941bddfd8 Binary files /dev/null and b/en/static/img/eip-patterns/message_endpoint_flow.png differ diff --git a/en/static/img/eip-patterns/message_filter_flow.png b/en/static/img/eip-patterns/message_filter_flow.png new file mode 100644 index 00000000000..4314665f5c7 Binary files /dev/null and b/en/static/img/eip-patterns/message_filter_flow.png differ diff --git a/en/static/img/eip-patterns/message_flow.png b/en/static/img/eip-patterns/message_flow.png new file mode 100644 index 00000000000..2381c084eb9 Binary files /dev/null and b/en/static/img/eip-patterns/message_flow.png differ diff --git a/en/static/img/eip-patterns/message_router_flow.png b/en/static/img/eip-patterns/message_router_flow.png new file mode 100644 index 00000000000..33d0868919b Binary files /dev/null and b/en/static/img/eip-patterns/message_router_flow.png differ diff --git a/en/static/img/eip-patterns/message_sequence_flow.png b/en/static/img/eip-patterns/message_sequence_flow.png new file mode 100644 index 00000000000..a121e816b2e Binary files /dev/null and b/en/static/img/eip-patterns/message_sequence_flow.png differ diff --git a/en/static/img/eip-patterns/message_store_design.png b/en/static/img/eip-patterns/message_store_design.png new file mode 100644 index 00000000000..ee30987c14a Binary files /dev/null and b/en/static/img/eip-patterns/message_store_design.png differ diff --git a/en/static/img/eip-patterns/message_store_flow.png b/en/static/img/eip-patterns/message_store_flow.png new file mode 100644 index 00000000000..b9d60948f1c Binary files /dev/null and b/en/static/img/eip-patterns/message_store_flow.png differ diff --git a/en/static/img/eip-patterns/message_translator_datamapper.png b/en/static/img/eip-patterns/message_translator_datamapper.png new file mode 100644 index 00000000000..a5cc153680f Binary files /dev/null and b/en/static/img/eip-patterns/message_translator_datamapper.png differ diff --git a/en/static/img/eip-patterns/message_translator_datamapper_item.png b/en/static/img/eip-patterns/message_translator_datamapper_item.png new file mode 100644 index 00000000000..61c027258ba Binary files /dev/null and b/en/static/img/eip-patterns/message_translator_datamapper_item.png differ diff --git a/en/static/img/eip-patterns/message_translator_flow.png b/en/static/img/eip-patterns/message_translator_flow.png new file mode 100644 index 00000000000..0fdba555cbc Binary files /dev/null and b/en/static/img/eip-patterns/message_translator_flow.png differ diff --git a/en/static/img/eip-patterns/message_types.png b/en/static/img/eip-patterns/message_types.png new file mode 100644 index 00000000000..b6035f2b7f3 Binary files /dev/null and b/en/static/img/eip-patterns/message_types.png differ diff --git a/en/static/img/eip-patterns/messaging_bridge_design.png b/en/static/img/eip-patterns/messaging_bridge_design.png new file mode 100644 index 00000000000..8b9e61a0c1f Binary files /dev/null and b/en/static/img/eip-patterns/messaging_bridge_design.png differ diff --git a/en/static/img/eip-patterns/messaging_bridge_flow.png b/en/static/img/eip-patterns/messaging_bridge_flow.png new file mode 100644 index 00000000000..7d9f0ebd557 Binary files /dev/null and b/en/static/img/eip-patterns/messaging_bridge_flow.png differ diff --git a/en/static/img/eip-patterns/normalizer_flow.png b/en/static/img/eip-patterns/normalizer_flow.png new file mode 100644 index 00000000000..8f983f85066 Binary files /dev/null and b/en/static/img/eip-patterns/normalizer_flow.png differ diff --git a/en/static/img/eip-patterns/pipes_and_filters_flow.png b/en/static/img/eip-patterns/pipes_and_filters_flow.png new file mode 100644 index 00000000000..c538be6716c Binary files /dev/null and b/en/static/img/eip-patterns/pipes_and_filters_flow.png differ diff --git a/en/static/img/eip-patterns/point_to_point_channel_design.png b/en/static/img/eip-patterns/point_to_point_channel_design.png new file mode 100644 index 00000000000..b4d4053513c Binary files /dev/null and b/en/static/img/eip-patterns/point_to_point_channel_design.png differ diff --git a/en/static/img/eip-patterns/point_to_point_channel_flow.png b/en/static/img/eip-patterns/point_to_point_channel_flow.png new file mode 100644 index 00000000000..5d222b5492a Binary files /dev/null and b/en/static/img/eip-patterns/point_to_point_channel_flow.png differ diff --git a/en/static/img/eip-patterns/process_manager_design.png b/en/static/img/eip-patterns/process_manager_design.png new file mode 100644 index 00000000000..8b50b8b31a7 Binary files /dev/null and b/en/static/img/eip-patterns/process_manager_design.png differ diff --git a/en/static/img/eip-patterns/process_manager_flow.png b/en/static/img/eip-patterns/process_manager_flow.png new file mode 100644 index 00000000000..655e0542eee Binary files /dev/null and b/en/static/img/eip-patterns/process_manager_flow.png differ diff --git a/en/static/img/eip-patterns/routing_slip_flow.png b/en/static/img/eip-patterns/routing_slip_flow.png new file mode 100644 index 00000000000..e9058325ae8 Binary files /dev/null and b/en/static/img/eip-patterns/routing_slip_flow.png differ diff --git a/en/static/img/eip-patterns/splitter_flow.png b/en/static/img/eip-patterns/splitter_flow.png new file mode 100644 index 00000000000..724aca9eff1 Binary files /dev/null and b/en/static/img/eip-patterns/splitter_flow.png differ diff --git a/en/static/img/eip-patterns/splitter_types.png b/en/static/img/eip-patterns/splitter_types.png new file mode 100644 index 00000000000..9319f4f857d Binary files /dev/null and b/en/static/img/eip-patterns/splitter_types.png differ