Thursday, July 10, 2025

Using OIC Events

In my opinion, OIC Events is a generally overlooked but very useful feature. In this blog post I describe how it works, a couple of typical use cases (as I see them) and a few considerations when using it.

Basically, there are 2 different types of event-based messaging:

  • Queues, which concerns a point-to-point model where the messages are delivered to a single consumer.
  •  Topics, which uses a publish/subscribe model where the producer broadcasts messages that can be consumed by multiple, subscribed consumers.

The main difference between event-based messaging on one hand and synchronous or fire&forget messaging on the other, is about coupling and scalability. In case of the first, messages can be produced even when no consumer is active, to be consumed later when the consumer does become active (guaranteed delivery). To prevent unconsumed messages from piling up, they typically expire after some time, so it is only guaranteed within that time-frame. Topics provide a looser coupled model than queues, as in case of a topic new subscribers can come and go. Event-based messaging interaction typically also scales better, as the consumer can handle messages in a sustainable pace even when there is a peak load in messages being produced.

Logically speaking OIC Events is an implementation of topics. So, the practical difference is this: when one Integration calls the other through an invoke (be that synchronous or fire&forget), then that other Integration must exist and be activated, otherwise the call will fail. In contrast, event-based Integrations are loose coupled, as in the only thing they share is the definition of the event that one publishes and the other subscribes to. Obviously, there is no point in publishing events when no Integration will ever listen, but the publisher should not care how many consumers there are nor whether any of those are activated at some point in time. 

Over time the structure of an event can be changed as long as it stays to be compatible with the event the consumer is expecting. For example, you should not remove an element in the published event that any consumer relies on, but you can add elements as required for new subscribers. An existing consumer can be refactored to take up the new definition of an event at some later point.

Sample Implementation

The following describes a simple use case, and consists of 1 Event, 1 Connection and 4 Integrations:

The Customer Creation event looks like this:


It has one custom header element called "region". Header elements are typically used for filtering, as I will show later.


I only have a 1 REST connection of type Trigger, nothing special about that.

There is 1 SMP Customer Creation Publisher Integration that publishes the customer creation event, and looks like this:


This publisher is triggered while passing in customer data:

{
  "address": "Main Street",
  "city": "Amsterdam",
  "postalCode": "1000AA",
  "name": "ACME",
  "houseNumber": "1",
  "region" : "North"
}

The Integration publishes the message and gives a (synchronous) response to indicate that publication was successful. As explained, this does not mean that it was consumed successfully, or that there is even a consumer activated.

Then I have 2 subscribers, the first being the SMP Customer Creator which consumes any message and then calls a SMP Customers integration to create the customer. Integrations that consume messages are asynchronous by definition and therefore don’t have a response.


The other subscriber SMP Customer Creation Notifier North consumes all messages where the “region” custom header element has value “North” and then sends an email to the customer representative of all the North region customers.

 

Message can be filtered based on header elements, using JQ expressions (JSON Query) https://docs.oracle.com/en/cloud/paas/application-integration/integrations-user/define-header-based-subscription-filtering.html

So, I also could have other subscribers for different regions that can have a different implementation, and even be in another project. I can also deactivate the subscriber for one region while keeping those for other regions active at the same time.

Typical Use Cases

Considering delivery is only “guaranteed” until the message expires, and guaranteed delivery is not the same as guaranteed execution, you should not use OIC Events when must-have downstream logic depends on it. When execution fails it may not be easy to recover from. Also, in case of multiple subscribers, it should be acceptable that messages are consumed in whatever order.

When both are of no concern, a typical use case is that of implementing logic that must be able to handle peak loads. You can imagine 1 integration that can be called many times in a short timeframe or fetches many rows at once from a database and instead of doing the processing itself it publishes a message for each individual call / row to be processed by some other subscribing Integration.

Another, related use case is to process logic in parallel. For example, after successful completion of some logic a group of users must be notified by email, while (in parallel) log events need to be persisted. The processing of one should not hinder the other.

Which brings me to the typical case I use it for, which is custom logging, especially to persist data on error. You typically don’t want to stack error handling on top of error handling and as persisting the logging may fail on its turn, I rather publish a message than invoking some other Integration that may not be activated or work properly. Moreover, in case of errors related to high load you would not add insult to injury by topping this with even more load and immediately start yet another Integration instance. Publishing a message instead give the engine the opportunity to handle it when that is has room to do so.

Another case is that of extendable business logic, for example where you want to be able to add future logic without having to change anything to the existing logic. Like in my example when a new region is added.

Also, a typical use case is to start an Integration on file creation at the File Server. The File Server generates system events for file creation, deletion, download, and folder create and delete. An Integration can subscribe to any of these events. You can imagine that over time new system events are introduced, although no example pops into my head right now.

Considerations

When using OIC Events there are the following considerations:

  • There is a maximum of 50 integrations that can subscribe to (any) event per OIC instance. So if you plan to implement an event-driven architecture in combination with many Integrations, then OIC Events is not the way forward. You should OIC Streaming instead, which nicely integrates with OIC.
  • You can only subscribe to a system event in case of an Integration in a Project.
  • For deactivated subscribers, events are retained for a maximum of 24 hours.
  • You can have a maximum of 10 custom headers. 
  • You can only publish to and subscribe from events within the same OIC instance. If you want to implement pub/sub use cases concerning multiple OIC instances, use OIC Stream instead.

 

Thursday, April 24, 2025

Oracle Process Automation: getting users in a group (Gen2 PCS to Gen3 OPA Upgrade: Part 9)

In this article I discuss how to get users in an IAM group to support task reassignment from a custom task list. It stands by itself as a topic but is also part of the series of articles in which I discuss some challenges you may face when upgrading Oracle Integration Gen2 (OIC 2) Process (PCS) to Gen3 (OIC 3) Oracle Process Automation (OPA) applications. The previous article can be found here.

Disclaimer: The below provides a snapshot at the time of writing. Things may have changed by the time you read it. It is also important to mention that this does not necessarily represent the view of Oracle.

Specifically for PCS to OPA migration you are also referred to the following:

When building a custom task list application for Oracle Process Automation, you may need to implement functionality to reassign a task to someone else who is in the IAM Group associated with the swimlane role the task is in. The same functionality is also available in the out-of-the-box workspace.

In PCS (OIC 2) you could use the /ic/api/process/v1/identities/groups/{groupid}/users API for this. Being part of the API’s the security of that is the same as for all others, and you cannot do more than the API is designed for, which mainly is to retrieve users, groups, and roles. Oracle Process Automation (OIC 3) does not have the equivalent of the /ic/api/process/v1/identities API’s. Instead, you are supposed to use the IAM API’s. 

The Challenge

Now here comes the challenge. To be able to populate a drop-down (or similar widget) with users you will call this API from the UI. To prevent the need of authorizing users to call this API with their own credentials, this is typically done using the OAuth 2.0 Client Credentials flow. This flow is then based on a client Confidential Application with at least the IAM User Administrator application role assigned to it, which allows adding, changing, and deleting users, groups and group memberships. Even though the UI may have been designed to only select users in a particular group, using browser developer tools, a more advanced user will be able to do any of those things. For most of my customers this is not acceptable from a security perspective and would block a go live of such an implementation.

The Remedy

Fortunately, the remedy is pretty straight-forward (albeit some work) as you can solve this by creating an Integration that serves as a proxy to IAM. It involves the following:

  1. Creation of a client Confidential Application in IAM with the User Administrator IAM application role assigned to it.
  2. Creation of a Integration Connection to call IAM REST API’s using the Client Credentials of the previous step.
  3. Creation of an Integration that returns the list of users in an IAM Group based on its display name.
The following explains each step in more detail.

1. Confidential Application Creation

The client Confidential Application is created in Cloud Console > Identity Security > Domains > [IAM domain associated with OPA] > Integrated Applications > Add Application.

The configuration looks as in the following screenshots. The parts indicated in red, is what you configure, the rest can be kept as defaulted.


2. Integration Connection Creation

The Connection in Integration is created as shown below. Mind the value of the ‘urn:opc:idm:__myscopes__’ scope, which needed to access the IAM admin API’s.


3. Integration Creation

Regarding the Integration there probably is more than 1 way to skin that cat, but I consider the following as a proper one.

The design looks as follows:
 

 

The /ic/api/process/v1/identities/groups/{groupid}/users takes the id of the IAM group as template (path) parameter. In the UI you don’t have a handle on that id and when promoting the application from one environment to the next, you probably deploy to a different domain at some point, and then that id will be different. But you can keep the group’s display name the same for all domains. Therefore, the interface of the Integration can be designed to look as follows:

Resource URI: /groups/{groupDisplayName}/users
Action: GET
Response:
{
  "items" : [ {
    "value" : "123xyz",
    "display" : "John Doe",
    "name" : jonh.doe@oracle.com
  } ],
  "count" : 1
}

The getGroupNameIAM activity in the Integration gets the group from IAM using its display name:

Resource URI: /Groups
Action: GET
Query Parameters:

  • filter (String)

Response:
{
  "Resources" : [ {
    "compartmentOcid" : "ocid1.tenancy.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "displayName" : "Sample Group",
    "domainOcid" : "ocid1.domain.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "id" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "urn:ietf:params:scim:schemas:oracle:idcs:extension:dynamic:Group" : {
      "membershipType" : "static"
    }
  } ],
  "totalResults" : 1
}

The ‘urn…’ element must be included for the Integration to interpret the response.

When mapping the groupDisplayName from the Integration’s request to this API you can use the following expression:

concat ('displayName eq "', /nssrcmpr:execute/nssrcmpr:TemplateParameters/ns21:groupDisplayName, '"' )

Configured like this should return exactly 1 group. Using that group’s id, the getUsersInGroupIAM activity gets all users in that group:

Resource URI: /Groups/{groupid}
Action: GET
Response:
{
  "members" : [ {
    "value" : "123xyz",
    "display" : "John Doe",
    "name" : "jonh.doe@oracle.com"
  } ],
  "urn:ietf:params:scim:schemas:oracle:idcs:extension:dynamic:Group" : {
    "membershipType" : "static"
  }
}

Again, the ‘urn…’ element in the response is just there for Integration to be able to interpret it. The mapping of it to the Integration’s response is pretty straight-forward:

 

The Default Fault Handlers is there to prevent the Integration from returning any detailed information from the backend, should it fail. I let it return a 401 in all cases. This being another best practice you should apply where it concerns security. The “not found” route returns a 404 when no IAM group can be found with the provided groupDisplayName.

Previous articles on the PCS to OPA migration subject:

Friday, January 03, 2025

Oracle Integration Gen2 PCS to Gen3 OPA Upgrade: Part 8

In series of articles, I discuss some challenges you may face when upgrading Oracle Integration Gen2 (OIC 2) Process (PCS) to Gen3 (OIC 3) Oracle Process Automation (OPA) applications. In this episode I discuss a challenge with the Boundary Error event handling. The previous article can be found here.

Disclaimer: The below provides a snapshot at the time of writing. Things may have changed by the time you read it. It is also important to mention that this does not necessarily represent the view of Oracle.

Specifically for PCS to OPA migration you are also referred to the following:

In OIC Process there are two ways to handle faults with integrations:

·       Use the out-of-the-box fault policies (the default when activating),

·       Model fault handling and deactivate fault policies.

As I already argued in the article that describes the Custom Fault Handling Pattern [http://kettenisblogs.blogspot.com/2022/10/oic-structured-process-custom-fault.html], the first option is probably not what is required. Instead, you may prefer using an Error Boundary event and model explicit fault handling as part of the process flow.

This is what I have done in a small PCS application to demo the working. First, I have modeled a Business Type with the name Fault that has attributes to hold fault information:

 

Next to the pretty standard code, summary and detail attributes, I also have attributes for a (unique) businessId and a timestamp for when the fault occurred. I then created a Business Exception with name IntegrationFault, based on the Fault Business Type. I then used the Business Exception to throw, catch and handle any fault in the process, like I did in the below Reusable Subprocess (applying the Service Handler pattern as I first introduced in an article about describing how to do “multi-threading” in PCS [http://kettenisblogs.blogspot.com/2022/02/oic-parallel-gateway-and-multi.html]):

 

Not shown in detail, but one of the Boundary Errors catches the RemoteFault and the other the BindingFault. Both are then forwarded to an Error End event raising the IntegrationFault Business Exception. Mind, the Service Handler does not handle the exception itself, which therefore is thrown back to the calling process:

As you can see, the calling process has an Event Subprocess that catches all IntegrationFaults. Granted, a bit overcomplicated for such a simple process, but in a real process application you probably have 2 or more integration calls and soon you will appreciate the Service Handler and Custom Fault Handling patterns 😉.

To easily showcase the result, I have put a Human Task in the Fault Handler, displaying the fault details.

For an APIInvocationError for a Bad Request this looks like this:


And for a Not Found like this:


In case of a 401 Unauthorized or 403 Forbidden, it looks like this:


Mind, this won’t return the 401 but instead a 500, which makes sense as this fault is caused by using wrong credentials as configured in OIC, so there is no point for the consumer to try again until this has been fixed on the OIC side (hence the 500). However, the actual, useful information is dug down in [CDATA] and it is not trivial to return in a easy-to-read way.

In case of the Integration not being available this looks like this:


As you can see, in this case the error is returned (by OIC) as an HTML page.

 

The challenge

When migrating an application handling Business Exceptions from PCS to OPA, you will find that the specific Exception caught by the Boundary Error events is not supported by OPA. This implies that any PCS process with a Boundary Error event that is configured to catch a specific error, cannot be (in-place) upgraded and instead must be refactored / migrated. I did not check the situation where you chose to catch all business and system exceptions, assuming you make all this effort to catch the error as specific as possible to simplify analyzing issues (compare with Java Exception versus NullPointerException, NumberFormatException, etc.).

 

The remedy

The good news is that this challenge can be addressed by introducing (what even results in) a simplification of the process, as unlike OIC 2 with its RemoteFault and BindingFault, in case of OIC 3 you only need to catch a single Service Invocation Failure to handle both.

So, in this model that got invalid after the import:

I can delete one of the Boundary Errors events and configure the other to catch the Service Invocation Failure:


The original IntegrationFault Business Exception has been migrated, although the result is not exactly the same. Any Business Exception has a code, message, and details attribute. The first two are fixed, but for details you can choose what type it should have, and when migrating from OIC 2 this became the Fault Business Type I created before. That Fault already had a code and message attribute of its own but that cannot be used in mappings (as you can see in the next picture).

You will find that the data association also became invalid as all source fields are different:


Completely new are the errorName and details.payload and details.uri elements, of which the payload contains the detailed error information. Not to lose the errorName along the way, I added it to my custom Fault Business Type. So, the mappings can be remedied as follows:


I don’t need to fix the mapping to the Error End event as for me that is just fine. Note how the fault is mapped 1:1 to the input.errorInfo.details, as that attribute is based on the same Business Type. The only data I need to add to it, are the businessId and the timestamp.


So, now my Service Handler is done, and I also don’t need to reconfigure the Event Subprocess as that catches the IntegrationFault that was migrated as-is, with its mapping based on the Fault Business Type:

Now let’s see how that works out runtime. I will skip discussing the change I had to make to the UI, as that is just to showcase the result. As you saw before, on OIC 2 I handled the 400 Bad Request. That gives practically the same result on OIC 3:


A 404 Not Found thrown by the backend, for example because it can’t find an city having a specific id, can also be handled the same way (picture concerning a Service Request but the idea is the same):


But from here one it has changed to what I consider to be an improvement. A 401 Unauthorized raised by the Integration’s Trigger connection is returned as a Service Invocation Failure which I can handle as the same way as all other faults. Notice though that there are payload details because the Integration never started:


An Invoke to a back-end using wrong credentials results in a oracle.cloud.connector.api.CloudInvocationException. When returned to the calling process using Fault Return, this also results in a Service Invocation Failure, this time with details (because the Integration was started, and I used the mapping to the Fault Return to propagate the details):


Another improvement (at least in my opinion) is the way that a deactivated or non-deployed Integration call is handled. This now results in a 404 Not Found, instead of the 503 Service Unavailable that OIC 2 used to return. You might consider not showing the detailed message, as that gives away the Integration’s URI:


 

Previous articles on the subject: