16 Anti-Patterns

An anti-pattern is a software design practice that is ineffective or counterproductive—in other words, the opposite of a "best practice." To put it another way, an anti-pattern is something that the software allows you to do, but that may have an adverse functional or performance impact.

Table 16-1 Anti-Patterns

Anti-Pattern Category Rationale Recommendation

Using REST APIs to extract bulk data. For example, Folios, Profiles, often specifying a large "limit" query parameter or a large date range.

Functional

Faster access with less operational impact on the hotel

The Oracle Hospitality APIs accommodate many use cases but were not designed for bulk data extract. It is recommended to use either an extract from Oracle Reporting and Analytics for bulk data use cases.

Multi-property shop against property availability API

Functional

Faster access with less operational impact on the hotel

The Oracle Hospitality OPERA APIs are optimized for resort-level resources. While it is possible to shop for availability across many different properties using the Oracle Hospitality OPERA APIs, it is recommended to use the Shop API which is specially designed for this purpose and returns data from a live cache.

For example, I want to be able to create a reservation for a guest at Hotel1, and then for the same guest book another reservation at Hotel2. The Shop API has the functionality available for you to look at availability across multiple properties.

Implementing multi-threaded Java application calling streaming API

Technical

Functional and scalable

The Streaming API adheres to the graphql-ws protocol which requires that a given stream receives a connection from only one source to preserve the ordering of events.

A stream is identified as a combination of the following: gateway + the chainCode + an application key.

For an example, refer to the Spring Boot documentation

Tip: Use a single WebSocketGraphQlClient instance for each server to have a single, shared connection for all requests to that server. Each client instance establishes its own connection, which is typically not the intent for a single server.

While we recommend using our GraphiQL tool or using Postman to solidify understanding of the Streaming API, it is important to use separate applications for GraphiQL, Postman, and your client code.  Similarly, your client code must be single threaded.

Our recommendation is to use a single-threaded application to consume the stream, then deploy a tool like Apache Kafka and multithreaded clients to consume events from Apache Kafka to populate back end systems.

Sending a token request with every API call

Functional

More cost effective for partners, protects identity servers

Performance impact and operational impact as a result of rate limiting by the identity servers

oAuth tokens have a lifetime of 60 minutes, and requesting an oAuth token is billable to integration partners. Our recommendation is to request a token only once every 59 minutes and implement code that caches and automatically renews the token every 59 minutes and stores it securely. Code that makes API calls can then use the cached token and be assured it is always valid.

postReservation, putReservation, postProfile and putProfile with invalid codes, such as rate, room type, source and market codes, address type, membershipType, and so on.

Functional

Corrupt data

OPERA Cloud is highly configurable and most of our OPERA Cloud customers leverage this to create the unique experiences offered by their resort.  This means that many configuration items will differ from one resort to another.

When creating or modifying reservations in OPERA Cloud, code valid to the resort must be used in the request body. For example, Market Code or Source Code. Failure to use codes valid for the property will result in reservations being created in OPERA Cloud with invalid codes. As soon as a user views the reservation, the user must update the reservation with valid codes.

To avoid this, integrators should use the List of Values Management and Enterprise Configuration APIs to determine the configuration particular to the resort they are calling.

An example is postReservation. When creating a new reservation, there are codes required as part of the request body. Prior to posting the reservation, ensure you have called the List Of Values, such as getSourceCode, getMarketCodes, getGuaranteeCodes, getMembershipTypes, and so on. The postman workflow samples we have offer an integrator a suggested set of operations to call prior to posting the new reservation. Please take a look at this to start building your integration.

Writing straight to back end databases after receiving an event

Technical

Overloads back end databases

OPERA Cloud can generate many thousands of events in certain circumstances, and the Streaming API is not throttled. If the streaming client is coded to write straight to a back end database, this can overwhelm the back end database.

To avoid this, implement a buffering mechanism such that events are consumed from the buffer before being written to the back end database. Ensure that the code reading from the buffer can scale to accommodate large numbers of events without overwhelming the back end database.

Call APIs directly from browsers or mobile apps

Technical

Security of credentials and data

The Oracle Hospitality Integration Platform APIs are certified only to be called from back end systems. This is partly a security posture and partly that the APIs are not optimized for mobile data restrictions.

If Oracle Hospitality APIs are needed as part of a browser or mobile app-based experience, implement a "Backend for Frontend (BFF)" pattern, which creates an abstraction layer consuming OHIP APIs and provides Experience APIs that are better suited to be called from mobile apps or web browsers.

Generating implementations tied to API specifications

Technical

Resilience against change

As business APIs, the Oracle Hospitality APIs contain a wealth of data. However, a given integration may need only some of that data.  To protect the integration against API changes, it is recommended to implement the "Tolerant Reader" pattern such that consuming code looks only at the fields needed by the implementation. While we always support backwards compatibility for v1 APIs, it is our goal to also support this for v0 APIs. However, changes can occur and the Tolerant Reader pattern can reduce the impact radius of these API changes.

Mapping Experience APIs 1:1 to OHIP APIs

Technical

Chatty, network heavy clients

When writing Experience APIs for consumption by mobile apps or web browsers, the APIs provided by Experience APIs do not need to map 1:1 to Oracle Hospitality APIs. We recommend using "API Composition" to gather all the required information from multiple Oracle Hospitality APIs together with the "Backends for Frontends" pattern to orchestrate multiple OHIP API calls but expose as a single Experience API.

Sending GET calls to either a collection or single resource repeatedly in case it has changed

Functional

Expensive for integration partners and not real time

Assuming Business Events are configured, every change that occurs within our Hospitality applications triggers a Business Event. Rather than continuously GETting a resource to see if it has changed, we provide the ability to consume Business Events as they occur. For Property APIs, see Business Events.

For example, my integration requires housekeeping information to be kept in sync with OPERA Cloud.  Rather than GETting the data using the HouseKeeping APIs, configure the business events for the housekeeping module. This will ensure events are generated and sent to the external system each time a resource is changed directly in OPERA Cloud. As soon as someone changes a room to Out of Order in OPERA, a business event will be generated for the external system.

Placing consuming architecture far from the OHIP gateway

Technical

Creates a high latency and poor consumer experience.

For the streaming API, this can result in a massive backlog of events that can never be consumed.

When implementing a Backends for Frontends (BFF) against a given OHIP API Gateway instance, verify that high levels of network latency do not exist between these two components. This is because latency can have a negative impact to users of the application connecting to OHIP. For example, fetching an OAuth token should take no longer than 100ms. For ultimate speed and low latency, consider implementing your BFF in the same region as the OHIP API Gateway and housing the OPERA Cloud instance(s) within the Oracle Cloud Infrastructure (OCI). Not only does this reduce latency, but it also increases security because the API traffic remains inside of Oracle Cloud.

Brittle handling of errors

Technical

Incomplete orchestration, unexpected results, operational impact on the hotel

Errors tend to be short lived, so to help create a fault tolerant consumer and a safe Backends for Frontends (BFF), use the circuit breaker pattern to retry the same API call when receiving an error. This is particularly important when orchestrating multiple OHIP API calls. When the resource is very large, the retry logic must be the following:
  • Retry the same call 30 minutes later

  • If the retry also times out, move the call to an error hospital and create a technical Service Request with all the details by following the process described in the Preface.

Specifying all fetch instructions on a resource

Functional

Slow performance

Many of the Oracle Hospitality OPERA Cloud APIs by default return only a subset of a resource or only the parent resource. For example, getProfile returns only the basic information about a person. Many of these APIs use a standard query parameter "fetchInstructions," which allows additional, often child, information on the resource to be returned.  By tailoring which additional pieces of information is returned to your use cases, you can increase the performance of and reduce the response body size of your API calls.

To achieve this, orchestrate using the "indicators" fetchInstruction that will show which child elements are filled, and then send a call listing only those child elements as fetchInstructions.

For example:

First call: /crm/v1/profiles?profilesIds={{profileId}}&fetchInstructions=Indicators

Then based on the results

Second call: /crm/v1/profiles?{{{profileId}}&fetchInstructions=Communication<additional fetchInstructions based upon the results of the first call>

Not using the "summaryInfo" query parameter

Functional

Slow performance

If only the summary information of a resource is needed, not child elements, then use the "summaryInfo" query parameter. This will both increase the performance of and reduce the response body size of your API calls.

For example:

/roomTypes?summaryInfo=true

Using Business Events to publish yield updates out to other systems

Functional

Performance impact to OPERA Cloud and to all external systems of that OPERA Cloud

Yield systems supply OPERA Cloud with updated rates to ensure price per room is optimized. This requires sending a large amount of price adjustments, each of which triggers many business events.

By default, changes made by one external system are not pushed to another external system. The "publisher" feature within OPERA Cloud enables this to be overridden.

However, the "publisher" feature must not be enabled on external systems in OPERA that supply yield updates because this will flood other external systems with needless rate updates and impact the timeliness of sending unrelated business events to those external systems.

As a revenue management system, using Synchronous REST APIs to fetch bulk data, or update bulk data. 

Functional

Performance impact

Revenue partners should be using asynchronous APIs to perform actions that will take some time for the OPERA Cloud database to action. For example, fetching a year's worth of reservations in OPERA Cloud. The Property APIs have asynchronous operations that cater to these business use cases. For more information, see Business Use Case.

Using APIs beyond their stated scope

Functional

Functional

Ensure you understand the scope of APIs by referencing the API specifications and any Business Use Case articles in the Oracle Hospitality Integration Platform developer portal.

Enabling business events for an external system in OPERA or in OHIP ahead of being ready to consume

Functional

Causes a large backlog of events, potentially choking the consumer. This can have an operational impact.

As soon as events are subscribed to an external system in OPERA — be it from the OPERA Cloud user interface or OHIP — the subscribed events will start to be enqueued. If not consumed, this will result in a very large queue, which is challenging for consuming systems to process.

Further, if the external system sends responses back to OPERA Cloud as a result of events received and is slow to process the events, then operational impact can occur as the state of the data in OPERA Cloud would differ from the state of the data perceived by an external system that is running behind.

To avoid this, when creating external systems in the OPERA Cloud user interface, configure the Business Events, but mark the external system inactive. Activate the external system only once the consuming architecture is ready. Similarly, subscribe to business events via the streaming API only when the consuming architecture is ready.

Not staying connected to the streaming API

Functional

Causes a large backlog of events, potentially choking the consumer.

The Streaming API is built upon WebSocket, where the connection remain open.  Barring network events, it is expected that WebSocket connections will remain open and connected permanently to the Streaming API, subject to disconnecting every 1 hour to refresh the oAuth token.

Disconnecting and then reconnecting some time later risks a large backlog of events queuing up, which can be challenging for the consuming architecture to process.

Incorrectly disconnecting from the streaming API

Functional

Connection will not re-open

When disconnecting from the Streaming API, it is important to follow the protocol and send the "Complete" message. After closing this, the connection can be closed. However, consuming systems must wait 500ms before reconnecting.

Consuming systems that do not follow this process will find the WebSocket does not re-open.

Not sending "ping" to the Streaming API

Functional

Connection closes

To keep the WebSocket connection, the consumer must send a non-billable "ping" every 15 seconds. For more information, see Keeping the Stream Open.  If the consumer does not send a ping, then the connection will automatically be closed after 30 seconds.

Disconnecting then reconnecting from the Streaming API starting from a given offset

Functional

Functional

Each business event sent on the streaming API is given a different offset number.  While these appear to increment, a linear progression is not guaranteed.  The offset number also changes if the consumer has been disconnected from the stream for over 24 hours.

It is recommended to remain connected. When you disconnect (for example to obtain a new oAuth token), reconnect without specifying an offset in the subscribe message. OHIP will resume sending events starting from the next event.

Two developers using the same application for streaming API

Functional

Functional

The Streaming API adheres to the graphql-ws protocol which requires that a given stream receives a connection from only one source to preserve the ordering of events.

A stream is identified as a combination of the following: gateway + the chainCode + an application key.

If two developers are trying to use the same application to access the Streaming API, they will lock each other out.

Instead, create one application per developer, then once development is complete, delete those development applications.

Using any cashierId in payment APIs

Functional

Impairs auditing payments

The cashierId usually represents a person at the front desk, but since it is possible to make payment changes using APIs, it is important to tie back the change to the organization or user who made the change. When multiple organizations use the same cashierId, it hinders auditing payments.

Contact the environment owner and ask the owner to allocate a cashierId to your organization. Only use this provided cashierId when calling payment APIs.