Create open data RPDE feeds live from your application.
Open Booking API is built on top of open data feed publishing, and RPDE feeds are therefore a prerequisite to implementing the rest of the API.
{% hint style="info" %} Even for systems that have already implemented OpenActive open data feeds, it is highly recommended that the feed generation code is migrated to within the OpenActive.Server.NET framework, using the steps below. This will greatly simplify the rest of the implementation, and increase maintainability. {% endhint %}
Before continuing with this tutorial, the following video offers a good theoretical understanding of the RPDE specification. Further information can be found in the developers guide and RPDE specification.
{% embed url="https://www.youtube.com/watch?v=yHZS24xzY-8" %}
In order for opportunities and offers within open RPDE feeds to be bookable via the Open Booking API, they must have IDs.
The Open Booking API is based on JSON-LD. JSON-LD IDs always take the form of a URL, and hence all IDs within the Open Booking API also take the form of a URL. The StoreBookingEngine
automatically constructs and parses these URLs for you, and deserialises them into POCOs. It also handles routing based on the URL template to the relevant store depending on the type of opportunity.
In order to take advantage of these features, you need to configure the URL structure specifically for your application.
{% hint style="info" %}
When making changes to these setting, specifically for IBookableIdComponents
, consider using Visual Studio's refactoring tools to alter the example provided, to ensure that the rest of the project still compiles.
{% endhint %}
The first URLs we'll set up are for the Opportunity and Offer pairs, such as the IDs found in the Open Booking API request snippet below:
"orderedItem": [
{
"@type": "OrderItem",
"acceptedOffer": {
"@type": "Offer",
"@id": "https://example.com/api/identifiers/api/session-series/100002#/offers/0"
},
"orderedItem": {
"@type": "ScheduledSession",
"@id": "https://example.com/api/identifiers/api/session-series/100002/scheduled-sessions/100003"
}
}
]
There are two key components that handle JSON-LD ID serialisation and deserialisation within the StoreBookingEngine
:
IBookablePairIdTemplate
instances specify the ID URL templates used by your application and associates them with OpenActive opportunity types, as well as the hierarchy of types that are in use from the OpenActive data model. They also define the types of opportunities that are bookable, and which feeds are used to publish the opportunities.IBookableIdComponents
are POCO objects that you define specifically based on the underlying data model within your booking system, used to deserialise the ID URL. The properties within these are mapped by name to the placeholders within the associatedIBookablePairIdTemplate
URL templates.IBookableIdComponents
represents an Opportunity and Offer pair, and hence includes both the Opportunity ID and Offer ID, with their matching overlapping components.
Note that the JSON-LD IDs do not need to resolve to actual endpoints, provided they are unique and exist within your domain.
IBookablePairIdTemplate
instances are configured in Startup.cs
or ServiceConfig.cs
within BookingEngineSettings
:
new BookingEngineSettings
{
// This assigns the ID pattern used for each ID
IdConfiguration = new List<IBookablePairIdTemplate> {
// Note that ScheduledSession is the only opportunity type that allows offer inheritance
new BookablePairIdTemplateWithOfferInheritance<SessionOpportunity> (
// Opportunity
new OpportunityIdConfiguration
{
OpportunityType = OpportunityType.ScheduledSession,
AssignedFeed = OpportunityType.ScheduledSession,
OpportunityIdTemplate = "{+BaseUrl}api/session-series/{SessionSeriesId}/scheduled-sessions/{ScheduledSessionId}",
OfferIdTemplate = "{+BaseUrl}api/session-series/{SessionSeriesId}/scheduled-sessions/{ScheduledSessionId}#/offers/{OfferId}",
Bookable = true
},
// Parent
new OpportunityIdConfiguration
{
OpportunityType = OpportunityType.SessionSeries,
AssignedFeed = OpportunityType.SessionSeries,
OpportunityIdTemplate = "{+BaseUrl}api/session-series/{SessionSeriesId}",
OfferIdTemplate = "{+BaseUrl}api/session-series/{SessionSeriesId}#/offers/{OfferId}",
Bookable = false
}),
new BookablePairIdTemplate<EventOpportunity>(
// Opportunity
new OpportunityIdConfiguration
{
OpportunityType = OpportunityType.Event,
AssignedFeed = OpportunityType.Event,
OpportunityUriTemplate = "{+BaseUrl}api/events/{EventId}",
OfferUriTemplate = "{+BaseUrl}api/events/{EventId}#/offers/{OfferId}",
Bookable = true
}),
...
}
}
Where you these referenced in the example above... | The following annotations apply... |
---|---|
SessionOpportunity and EventOpportunity |
Examples of an IBookableIdComponents POCO used to map to placeholders within the ID templates defined |
BookablePairIdTemplate |
A template that contains a hierarchy of up to three OpenActive types, in order from child to parent (e.g. ScheduledSession → SessionSeries → EventSeries ). BookablePairIdTemplate accepts the generic parameter of an IBookableIdComponents type, which binds the placeholders within the URL templates specified to the properties defined within the IBookableIdComponents POCO. |
BookablePairIdTemplateWithOfferInheritance |
A specialism of **** BookablePairIdTemplate designed for the special case of ScheduledSession → SessionSeries where Offers may be inherited from SessionSeries to ScheduledSession . |
OpportunityType |
The OpenActive opportunity type represented |
AssignedFeed |
The feed within which this opportunity type is published (for example, the EventSeries may be embedded within a SessionSeries feed as shown in this example). |
|
The URL Templates used to serialise/deserialise the POCO for an Opportunity ID and Offer ID. Note that the Opportunity ID and Offer ID are deserialised into the same POCO, as an Opportunity and Offer pair, and hence any shared components within these must match (e.g. an Offer is specific to an Event, and hence will include both the Event ID and and |
Bookable |
Whether the specified Opportunity and Offer pair is bookable within this booking system using the Open Booking API. Note that SessionSeries and FacilityUse are not bookable within the Open Booking API, which instead prefers ScheduledSessions or Slots . |
BaseUrl |
This is a placeholder within the ID templates that has a special function of ensuring conformity with the JsonLdIdBaseUrl setting within BookingEngineSettings , both for serialisation and deserialisation. |
IBookableIdComponents
are defined by the classes in the IdComponents
directory.
These classes must be created by the booking system. There is a choice of string
, long?
and Uri
available as the type for each component of the ID, as well as any enum
type. The names of the components must exactly match the placeholders within the associated IBookablePairIdTemplates
.
public class SessionOpportunity : IBookableIdComponentsWithInheritance
{
public OpportunityType? OpportunityType { get; set; }
public OpportunityType? OfferOpportunityType { get; set; }
public long? SessionSeriesId { get; set; }
public long? ScheduledSessionId { get; set; }
public long? OfferId { get; set; }
}
In the example above | Description |
---|---|
OpportunityType |
The OpportunityType represents the type in the hierarchy that the Opportunity and Offer pair represents (e.g. HeadlineEvent or its child Event ). This property is available by the class extending either IBookableIdComponents or IBookableIdComponentsWithInheritance . |
OfferOpportunityType |
For SessionSeries , where the Offer can be inherited to the child ScheduledSession , OfferOpportunityType indicates which Offer is being referenced - the parent or the child. This property is only available by the class extending IBookableIdComponentsWithInheritance . |
The Seller IDs are configured slightly differently to the Opportunity and Offer pair IDs, for simplicity.
There is a built-in POCO named SellerIdComponents
that is defined as follows:
public class SellerIdComponents
{
public long? SellerIdLong { get; set; }
public string SellerIdString { get; set; }
}
Within BookingEngineSettings
the SellerIdTemplate
setting controls how the Seller ID is serialised and deserialised. There are two options for Seller IDs: Single Seller and Multiple Seller.
Booking systems supporting Multiple Sellers
Depending on the type of your internal Seller ID, you may use either SellerIdLong
or SellerIdString
within the URL template. Once you have chosen which one to use, simply reference that same property consistently wherever you use the Seller ID throughout your code, and ignore the other property.
The following example demonstrates BookingSystemSettings
for Multiple Sellers:
SellerIdTemplate = new SingleIdTemplate<SellerIdComponents>(
"{+BaseUrl}api/sellers/{SellerIdLong}"
),
Booking systems supporting a Single Sellers
To use Single Seller mode, simply use neither SellerIdLong
or SellerIdString
within the URL template, and set HasSingleSeller
to true
. Then simply do not reference SellerIdComponents
anywhere in your code, as both SellerIdLong
and SellerIdString
will be null. RenderSingleSellerId
is provided for scenarios where a Single Seller ID needs to be rendered.
The following example demonstrates BookingSystemSettings
for a Single Seller:
HasSingleSeller = true,
SellerIdTemplate = new SingleIdTemplate<SellerIdComponents>(
"{+BaseUrl}api/seller"
),
The StoreBookingEngine
handles the serialisation and parameter validation that would usually be required when implementing an RPDE feed. All that is required for each feed is to implement a single method within an IOpportunityDataRPDEFeedGenerator
class.
{% hint style="info" %} If you have already implemented RPDE feeds within your application using OpenActive.NET, your existing database queries and OpenActive model mapping should easily transferable to within the generator method. {% endhint %}
Within BookingEngineSettings
within EngineConfig.cs
the OpenDataFeeds
setting configures the routing to the different feed generators from the GetOpenDataRPDEPageForFeed
method being called in the controller.
OpenDataFeeds = new Dictionary<OpportunityType, IOpportunityDataRPDEFeedGenerator> {
{
OpportunityType.ScheduledSession, new AcmeScheduledSessionRPDEGenerator()
},
{
OpportunityType.SessionSeries, new AcmeSessionSeriesRPDEGenerator()
},
{
OpportunityType.FacilityUse, new AcmeFacilityUseRPDEGenerator()
}
,
{
OpportunityType.FacilityUseSlot, new AcmeFacilityUseSlotRPDEGenerator()
}
},
Three implementations of IOpportunityDataRPDEFeedGenerator
are available depending on your prefered RPDE Ordering Strategy, and whether your ID is a long
or string
.
Using the example of the IBookableIdComponents
class SessionOpportunity
from Step 2, and the OpenActive feed root type of ScheduledSession
, a generator is created by subclassing one of the following:
RPDEFeedModifiedTimestampAndIDLong<SessionOpportunity, ScheduledSession>
RPDEFeedModifiedTimestampAndIDString<SessionOpportunity, ScheduledSession>
RPDEFeedIncrementingUniqueChangeNumber<SessionOpportunity, ScheduledSession>
Any of the above would require the same GetRPDEItems
method be implemented, as below:
public class AcmeScheduledSessionRPDEGenerator :
RPDEFeedModifiedTimestampAndIDLong<SessionOpportunity, ScheduledSession>
{
protected override List<RpdeItem<ScheduledSession>> GetRPDEItems(long? afterTimestamp, long? afterId)
{
...
}
}
The appropriate query for the database for your chosen RPDE Ordering Strategy must be used, and the ordering of the items returned from your overridden method is automatically validated to ensure consistency with the specification.
Within the mapping of your data to the OpenActive model, there are a few helper methods available from the base class to construct IDs specific for the feed:
Method | Example |
---|---|
RenderOpportunityId |
|
RenderOfferId |
|
(for Multiple Sellers) |
|
|
Id = this.RenderSingleSellerId(), |
The DatasetSiteGeneratorSettings
within EngineConfig.cs
can be used to configure your dataset site.
Only the OpenDataFeedBaseUrl
is used by the StoreBookingEngine
, so this must be accurate to proceed with testing.
All other settings are described within the documentation for OpenActive.DatasetSite.NET.
{% hint style="info" %}
If you have already implemented a dataset site within your application using OpenActive.DatasetSite.NET, the settings should be easily transferable to EngineConfig.cs
.
{% endhint %}
If you run your application and navigate to the dataset site endpoint (e.g. https://localhost:44307/openactive), you should find it now reflects your updated settings, and the links to the RPDE pages on that page work as expected.