openactive / modelling-opportunity-data

OpenActive Modelling Opportunity Data specification
https://www.openactive.io/modelling-opportunity-data/
Other
6 stars 6 forks source link

ScheduledSession <-> Event reclassification #264

Open nickevansuk opened 3 years ago

nickevansuk commented 3 years ago

Background

The types of opportunities in OpenActive have a specific semantic meaning:

Within booking systems, the difference between an Event and a ScheduledSession is usually as subtle as whether the Event is part of a recurrence rule - often the user can tick a box to say "repeat every Tuesday" as they can in Google Calendar, and an Event then becomes a ScheduledSession.

The current guidance for this scenario is for the booking system to remove the Event from their Events feed, and add it into their SessionSeries/ScheduledSessions feed.

According to Bookwhen this is not an uncommon occurrence, e.g. an organiser scheduling the first Event, and then deciding to add a recurrence to it afterwards for it to become part of a SessionSeries. Potentially bookings have already been made against the Event when this happens.

Challenges

Within the booking system an Event and a SessionSeries/ScheduledSession are usually represented by the same underlying structure. Forcing the same underlying structure to output two feeds can create extra work for the booking system, and also is in opposition to one of the principles of RPDE (which exists for the same reason):

Does your implementation match your internal state as closely as possible (i.e. you are not generating sessions that don't actually yet exist as records in your system from a recurrence rule, but are instead providing the recurrence rule data directly). [ref]

When an Event becomes a ScheduledSession and visa versa, the current guidance implies that the item be marked as deleted in its current feed, and a new item (with a new @id) be added to a new feed. When the Open Booking API is implemented, changing the @id of an item will break Change of Logistics Notifications (openactive/open-booking-api#138) (e.g. if an Event becomes a ScheduledSession then its location is updated).

Potential solutions

If we want to retain the semantics of Event and its subclasses, given that it is possible for systems to switch between the types after bookings have been made, one potential solution is to allow an opportunity to have its @type changed, while maintaining its @id.

The feed split could then be:

When an Event becomes a ScheduledSession in the second feed, it is then linked to the schedule it is now part of (SessionSeries). When a ScheduledSession becomes an Event in the second feed, it is effectively unlinked from the schedule, as it is a one-off session.

(N.B in the case of Bookwhen this doesn’t actually help as their Events feed and ScheduledSession feed are driven from two different underlying database tables)

Further investigation is required to determine whether having a JSON-LD object change @type while maintaining its @id is (a) allowable and (b) best practice

nickevansuk commented 3 years ago

Further note on this:

The reason for reflecting the structure of C1, C2, P and B endpoints for @id references simplifies use of the model for both requests and responses, e.g:

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": {
        "@type": "Offer",
        "@id": "https://example.com/events/452#/offers/878"
      },
      "orderedItem": {
        "@type": "ScheduledSession",
        "@id": "https://example.com/events/452/subEvents/132"
      }
    }
  ]

This is in contrast with the referencing approach used in feeds, which uses the @id as a direct reference e.g. for superEvent below:

"data": {
  "@context": "https://openactive.io/",
  "@type": "ScheduledSession",
  "@id": "https://id.ourparks.org.uk/api/session-series/1234#/subEvent/C5EE1E55-2DE6-44F7-A865-42F268A82C63",
  "superEvent": "https://id.ourparks.org.uk/api/session-series/1234",
  ...
}

A move towards internal consistency would be to use the following in the Orders feed (and also potentially in C1, C2, B, P etc too):

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": "https://example.com/events/452#/offers/878",
      "orderedItem": "https://example.com/events/452/subEvents/132"
    }
  ]

A move towards external consistency would be to use the following in all feeds and APIs (as per this example):

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": { "@id": "https://example.com/events/452#/offers/878" },
      "orderedItem": { "@id": "https://example.com/events/452/subEvents/132" } 
    }
  ]

Both of the above approaches are valid JSON-LD (https://www.w3.org/TR/json-ld/#advanced-concepts), however the latter does not require changes to the @context to support it in different scenarios (though the changes to @context would be trivial to make, and are already set on several OpenActive terms) (N.B. this is not a comment on the validity of changing the @type)

Although @type in the Orders Feed can help data consumers to match the @id to the relevant feed, a data consumer will want to compare all received @id to their stored Orders rather than the other way around, which means this is unlikely to be used for this purpose.

nickevansuk commented 3 years ago

Given that @type is optional in JSON-LD, can be an array, and in some cases can be inferred (https://www.w3.org/TR/json-ld/#specifying-the-type), it seems that reassigning the @type is not an issue here?

nickevansuk commented 3 years ago

An issue with using the same @id namespace across multiple feeds is that RPDE is not designed to resolve conflicts between feeds - the modified timestamp is only local to the feed (and globalising the scope of modified creates sync issues for data users consuming multiple parallel feeds, as well as breaking the RPDE transport-level abstraction). Therefore each feed requires a unique @id namespace to ensure that the latest data is discernible to the data user.

Another option here is to simply use the Replacement within the Orders feed when a booked event changes type. This would mean that the @type and @id could then be easily updated when an Event is recategorised. This would trigger a notification to the user by default, however.

thill-odi commented 3 years ago

Reviewing this thread, it doesn't appear to me that there are any good options for changing @type unobtrusively.

The chief difficulty is that at the very least we're forcing data consumers to consume all feeds, on the off-chance that one of the items they've harvested has changed @type and henceforth jumped feeds.

It would be possible to get around this to some degree by ensuring all changes are listed in the Orders feed, but problems of modelling arise: if the change from e.g. Event to CourseInstance includes the addition of Course-specific data, consumers developed to process Events will not be able to parse the relevant data. This points to a larger problem of differentiating between cases where the change in @type is trivial (and hence shouldn't trigger operations knock-ons such as automated emails, cancellations, etc.) and where it's significant (and therefore should).

On reflection, the guidance should simply be that changes in @type result in:

This implies that all @type changes will be significant: bookings will be cancelled in the event of a @type change and so forth. Given the difficulty of automatically determining which changes should be considered significant and which not, however, this is preferable to the alternative of treating all changes as insignificant.

Interfaces that allow users to change @type easily, however, should probably add guidance or e.g. modal dialogue boxes warning users of the possible consequences of @type changes.

nickevansuk commented 3 years ago

Further alternative solutions are considered below:

Potential Solution 1: Represent the “Event” as a SessionSeries with a Schedule with repeat count 1

Issues:

Potential Solution 2: Represent the “Event” as a SessionSeries without a Schedule and with a single ScheduledSession, and rename “SessionSeries” to something that infers both.

Advantages:

Issues:

Potential Solution 3: “Delete and recreate the opportunity” with a new opportunity using a new @type and @id. Use a mechanism similar Open Booking API “Replacement” when an opportunity changes an @type and @id to update the data user with the new @id

Advantages:

Issues:

Potential Solution 4: Combine types in the same feed - e.g. SessionSeries and Event, sharing the @id between types

Would still require separate RPDE items and deletion for the different types within the same feed unless @ids could be reused between types in the same feed

Advantages:

Issues:

Potential Solution 5: Keep the same @id and change the @type, meaning that items jump between the current structure of feeds

Advantages:

Issues:

Potential Solution 6: Keep the same @id and change the @type, and unify feeds around parents/child: so one feed of Event+ScheduledSession, another for CourseInstance+SessionSeries

Allow types to change within the same feed, just as updates. This lessens the importance of the “type” attribute for how it is when storing data.

Could prioritise the latest @id based on the “modified” timestamp for conflict resolution, which means it doesn’t matter which feed it comes from (though processing “delete” across feeds is difficult - if one feed deletes it and it appears in the other, what then? RPDE doesn’t currently support cross-feed deletes)

Issues:

Potential Solution 7: "Delete and recreate the opportunity” with a new opportunity using a new @type and @id. Force Seller-requested Cancellation when an opportunity changes classification, and warn the admin user in advance of this happening to discourage them doing so for an event that already has bookings associated

Advantages:

Issues

Potential Solution 8: “Replacement” includes further Change Of Logistics notifications embedded in the Orders feed

Simple implementation:

Advantages:

Issues:

Summary

Potential Solution 7 and Potential Solution 8 have the least number of issues and complexity.

Hence, if both were supported, the tradeoff for the booking system is between: