Audited Services Stack Overflow allows collaborative editing. Let's walk through an example where Alice creates a new question and Bob edits the text. Since all audit features are on, both versions of the question are retained and can be fetched through services. Create Alice POSTs a new question to on December 31. She gets back 201 Created and a location header Location: http://.../api/question/1. She can GET http://.../api/question/1 to get the full body. was set to 1. The next created question will be 2. The next Answer will be 1. Each type gets its own sequence. was set to December 31. is null, indicating that there is no phase-out time; that this data is currently active. matches for now. matches for now. The audit properties appear duplicated on the version. Update Bob PUTs a new version to the next day, on January 1. The version number is present and matches, so the edit succeeds. He gets back 204 No Content. He can GET http://.../api/question/1 to get the full body. was set to January 1. is null again. Version 1 (which we'll look at next) had its updated to the same time, January 1. The version number was updated to 2. was updated to Bob. and are unchanged. Read by version To read old versions, we can enhance the read service with an optional version parameter and an optional criteria. Leaving off the version query parameter would give the latest version. But now we can GET http://.../api/question/1?version=1 to see the previous version. Everything is identical as when Alice first created it, except for . It started as and was changed to January 1, the time version 2 was created. turns the data store into an immutable append-only log. The only in-place updates are to and the only possible edit is to phase out a version (by changing from to the current instant) while simultaneously phasing in a new version at the same instant. Or, in the case of a delete, not phasing in any new version. Read by time While it's usually more convenient to deal with version numbers, we can also query for old versions by time. Now we can GET http://.../api/question/1?system=2018-01-01T00:00:00.000Z to see the previous version. Time ranges are inclusive on the end and exclusive on the end. So any time within the range would return the first version. The time range check is fairly complex at the data layer. If we're querying for the current version (the system parameter is not provided or is null) then we have to check that systemTo is also null. Otherwise we have to check that the system parameter falls between systemFrom and systemTo, also taking into account the fact that systemTo may be null. We could have defined the additional criteria like this: The synthetic property exists to simplify criteria like this, and to use in association criteria as described in Audit Model. Thus we're able to simplify the optionalCriteria to just . /api/question id systemFrom systemTo createdOn systemFrom createdById lastUpdatedById /api/question/1 systemFrom systemTo systemTo lastUpdatedById createdById createdOn service QuestionResource { getById(id: Long[1..1]): QuestionReadProjection[0..1] { operation : read; url : /question/{id: Long[1..1]}?{version: Integer[0..1] version}; criteria : this.id == id; optionalCriteria: this.version.number == version format : json; } } systemTo null systemTemporal systemTo systemTo null service Question { getById(id: Long[1..1]): QuestionReadProjection[0..1] { operation : read; url : /question/{id: Long[1..1]}?{version: Integer[0..1] version}&{system: TemporalRange[0..1] system}; criteria : this.id == id; optionalCriteria: this.version.number == version optionalCriteria: this.system == system format : json; } } from to [2017-12-31T23:59:59.000Z, 2018-01-01T23:59:59.000Z) this.systemTo == null && system == null || system != null && this.systemFrom <= system && (this.systemTo == null || system < this.systemTo) system this.system == system