rack-service_api_versioning 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +14 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +105 -0
- data/Rakefile +60 -0
- data/bin/console +14 -0
- data/bin/setup +12 -0
- data/config.reek +21 -0
- data/doc/API-DOCUMENTATION.md +166 -0
- data/doc/CODE_OF_CONDUCT.md +74 -0
- data/doc/UBIQUITOUS-LANGUAGE.md +286 -0
- data/lib/rack/service_api_versioning/accept_content_type_selector.rb +78 -0
- data/lib/rack/service_api_versioning/api_version_redirector.rb +60 -0
- data/lib/rack/service_api_versioning/build_redirect_location_uri.rb +55 -0
- data/lib/rack/service_api_versioning/build_redirect_uri_from_env.rb +54 -0
- data/lib/rack/service_api_versioning/encoded_api_version_data/input_data.rb +45 -0
- data/lib/rack/service_api_versioning/encoded_api_version_data/invalid_base_url_error.rb +22 -0
- data/lib/rack/service_api_versioning/encoded_api_version_data/return_data.rb +44 -0
- data/lib/rack/service_api_versioning/encoded_api_version_data.rb +61 -0
- data/lib/rack/service_api_versioning/http_error_response.rb +29 -0
- data/lib/rack/service_api_versioning/input_env.rb +38 -0
- data/lib/rack/service_api_versioning/input_is_invalid.rb +36 -0
- data/lib/rack/service_api_versioning/match_header_against_api_versions.rb +55 -0
- data/lib/rack/service_api_versioning/report_invalid_description.rb +19 -0
- data/lib/rack/service_api_versioning/report_no_matching_version.rb +34 -0
- data/lib/rack/service_api_versioning/report_not_found.rb +18 -0
- data/lib/rack/service_api_versioning/service_component_describer/report_service_not_found.rb +44 -0
- data/lib/rack/service_api_versioning/service_component_describer.rb +68 -0
- data/lib/rack/service_api_versioning/version.rb +7 -0
- data/lib/rack/service_api_versioning.rb +14 -0
- data/lib/tasks/flog_task_patch.rb +12 -0
- data/rack-service_api_versioning.gemspec +62 -0
- data/tmp/gemsets/setup-and-bundle.sh +48 -0
- metadata +415 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
# Ubiquitous Language
|
2
|
+
|
3
|
+
Every application and system incorporates some amount and type of *domain knowledge*, originally expressed in terminology familiar to domain experts as having specific meanings that are often meant to be an unambiguous shorthand for broader concepts. Too often, development teams implementing these software artefacts use different, less precise and/or specific terms, the variations between which introduce confusion between [Stakeholders](#stakeholder). Having an evolving but precisely-defined, uniformly-shared, yet evolving [ubiquitous language](http://blog.carbonfive.com/2016/10/04/ubiquitous-language-the-joy-of-naming/) eases development and improves communication by ensuring that "everybody means what they say they mean", preventing miscommunication and ambiguity from introducing defects and delays into what is already an imperfect, probably late, system.
|
4
|
+
|
5
|
+
The Ubiquitous Language (UL) for `Rack::ServiceApiVersioning` **must** define *all* terms used in specification, design, code comments, or other communication between [Stakeholders](#stakeholder) (possibly between a given Stakeholder and his or her future self) which lack, or differ from, a single, specific definition in standard technical written English. *This includes* terms that are primarily used in a technical context where the meaning of those terms is ordinarily open to multiple interpretations or meanings, as demonstrated by perusing the industry technical literature.
|
6
|
+
|
7
|
+
The Ubiquitous Language for `Rack::ServiceApiVersioning` **must** be compatible with, and is presumed to be a proper subset of, the UL for any application and/or [Platform](#platform) that uses it. Deviations from this are highly likely to cause miscommunication and misunderstanding due to ambiguity during the development of that larger application/Platform.
|
8
|
+
|
9
|
+
----
|
10
|
+
|
11
|
+
## Contents
|
12
|
+
|
13
|
+
- [The Rules](#the-rules)
|
14
|
+
- [Capitalisation](#capitalisation)
|
15
|
+
- [Exception for Requirement Level Keywords (RFC 2119)](#exception-for-requirement-level-keywords-rfc-2119)
|
16
|
+
- [Additional Considerations for Requirement Level Keywords (RFC 2119)](#additional-considerations-for-requirement-level-keywords-rfc-2119)
|
17
|
+
- [Common Words in Technical English](#common-words-in-technical-english)
|
18
|
+
- [Non-Normative Supplemental Information](#non-normative-supplemental-information)
|
19
|
+
- [Use of Temporary Placeholders](#use-of-temporary-placeholders)
|
20
|
+
- [Term Context to be Reflected in Definition](#term-context-to-be-reflected-in-definition)
|
21
|
+
- [Basic Policy](#basic-policy)
|
22
|
+
- [Terms that Differ Between Contexts](#terms-that-differ-between-contexts)
|
23
|
+
- [Highlighting Context with Multiple Definitions](#highlighting-context-with-multiple-definitions)
|
24
|
+
- [Ordering of Multiple Contexts](#ordering-of-multiple-contexts)
|
25
|
+
- [Meaning of Term Affected By Other Contexts](#meaning-of-term-affected-by-other-contexts)
|
26
|
+
- [Term Closely Tied to Other Term(s)](#term-closely-tied-to-other-terms)
|
27
|
+
- [Transitional Meaning of a Term](#transitional-meaning-of-a-term)
|
28
|
+
- [Use of Term as Synonym (Cross-Reference)](#use-of-term-as-synonym-cross-reference)
|
29
|
+
- [Link Formation](#link-formation)
|
30
|
+
- [Link on First Use in a Paragraph](#link-on-first-use-in-a-paragraph)
|
31
|
+
- [Optional Abbreviation After First Use in a Paragraph](#optional-abbreviation-after-first-use-in-a-paragraph)
|
32
|
+
- [Use Non-Abbreviated Form on First Use in Subsequent Paragraph](#use-non-abbreviated-form-on-first-use-in-subsequent-paragraph)
|
33
|
+
- [The List](#the-list)
|
34
|
+
- [Accept](#accept)
|
35
|
+
- [API Version](#api-version)
|
36
|
+
- [AVIDA](#avida)
|
37
|
+
- [API Version-Independent Delivery Application](#api-version-independent-delivery-application)
|
38
|
+
- [Component Service](#component-service)
|
39
|
+
- [Content Negotiation](#content-negotiation)
|
40
|
+
- [Deprecate](#deprecate)
|
41
|
+
- [Endpoint](#endpoint)
|
42
|
+
- [Entity](#entity)
|
43
|
+
- [Glossary](#glossary)
|
44
|
+
- [PDA](#pda)
|
45
|
+
- [Platform](#platform)
|
46
|
+
- [Primary Delivery Application](#primary-delivery-application)
|
47
|
+
- [Register](#register)
|
48
|
+
- [Request](#request)
|
49
|
+
- [Repository](#request)
|
50
|
+
- [Service](#service)
|
51
|
+
- [Service Base URL](#service-base-url)
|
52
|
+
- [Service Description](#service-description)
|
53
|
+
- [Service Endpoint](#service-endpoint)
|
54
|
+
- [Specification](#specification)
|
55
|
+
- [Stakeholder](#stakeholder)
|
56
|
+
- [Target Service](#target-service)
|
57
|
+
- [Value Object](#value-object)
|
58
|
+
- [Yank](#yank)
|
59
|
+
|
60
|
+
----
|
61
|
+
|
62
|
+
## The Rules
|
63
|
+
|
64
|
+
### Capitalisation
|
65
|
+
|
66
|
+
Ubiquitous Language terms **must** always have each word capitalised (e.g., Member Name). The definition **must** be in the singular for nouns, and third person simple present usage for verbs. Usage may be modified according to the rules of standard English as is normal for nouns (e.g., [Stakeholders](#stakeholder) is a collection of possibly multiple individuals, each of whom is a Stakeholder), verbs (e.g., [Requested](#request) as the simple past tense of Request), and so on. Such usage highlights that a Ubiquitous Language term is being discussed which has a meaning distinct from any it may have in standard English.
|
67
|
+
|
68
|
+
#### Exception for Requirement Level Keywords (RFC 2119)
|
69
|
+
|
70
|
+
Words defined in [RFC 2119](http://www.faqs.org/rfcs/rfc2119.html) as specifying interpretation of requirement levels, such as **must**, **may**, and **should**, **should not** be capitalised but instead rendered in **boldface**, following industry custom.
|
71
|
+
|
72
|
+
### Additional Considerations for Requirement Level Keywords (RFC 2119)
|
73
|
+
|
74
|
+
Words defined in [RFC 2119](http://www.faqs.org/rfcs/rfc2119.html) as specifying interpretation of requirement levels within a specification **should not** be linked to entries in this [Glossary](#glossary). Rather, the opening sections of the Specification or other document in question should incorporate the phrase recommended by the RFC, as follows (reformatted from the original):
|
75
|
+
|
76
|
+
> The key words **must**, **must not**, **REQUIRED**, **shall**, **shall not**, **should**, **should not**, **RECOMMENDED**, **may**, and **OPTIONAL** in this document are to be interpreted as described in [RFC 2119](http://www.faqs.org/rfcs/rfc2119.html).
|
77
|
+
|
78
|
+
### Common Words in Technical English
|
79
|
+
|
80
|
+
Words that occur and are used in the same sense as in the general technical literature, such as *device* or *text,* are not part of the Ubiquitous Language. If a specific context would be served by applying a more specific or variant definition of a term, it should be consistently distinguished from the generic term. Hence, *device* as opposed to a hypothetical *Frobulation Device*.
|
81
|
+
|
82
|
+
### Non-Normative Supplemental Information
|
83
|
+
|
84
|
+
Paragraphs in the definition of a term **may** be added that convey (primarily out-of-domain) additional supplemental information relevant to a term. These paragraphs **must** be added to the end of the discussion of a term, and **must** be preceded by a paragraph consisting solely of the text *Non-normative supplemental information follows.*
|
85
|
+
|
86
|
+
Such non-normative supplemental information **may** omit links to definitions of terms used that were linked to in the earlier,l normative section of the term definition.
|
87
|
+
|
88
|
+
### Use of Temporary Placeholders
|
89
|
+
|
90
|
+
As the Ubiquitous Language evolves and grows, and is recorded in this [Glossary](#glossary), a new definition may be added which references other terms not yet having Glossary entries. When circulating the draft Glossary to others for review and comment, those referenced entries **should** be created and linked to in the new entry. The Glossary maintainer **should** complete those definitions as well, but **may** instead choose to create an entry with the text "TBD." as the complete content. This is an indication to other reviewers that the term so listed is known to be relevant but has not yet been defined in the Glossary.
|
91
|
+
|
92
|
+
All [Glossary](#glossary) definitions relevant to a given artefact (and to those terms' definitions, and so on) **must** be defined in the Glossary before a Specification for that artefact is completed or meaningful code developed.
|
93
|
+
|
94
|
+
### Term Context to be Reflected in Definition
|
95
|
+
|
96
|
+
#### Basic Policy
|
97
|
+
|
98
|
+
Since the Ubiquitous Language includes terms that are defined in a domain context, a technical context, or both, each term **must** indicate at the beginning of its definition, the phrase *Domain term.* for domain terms or *Technical term.* for terms which are not relevant to a domain expert but have a specific meaning to technical [Stakeholders](#stakeholder).
|
99
|
+
|
100
|
+
#### Terms that Differ Between Contexts
|
101
|
+
|
102
|
+
Terms that have importantly different meanings between contexts should be defined sequentially under the same term, with the domain context first.
|
103
|
+
|
104
|
+
##### Highlighting Context with Multiple Definitions
|
105
|
+
|
106
|
+
When a term has definitions supplied for both contexts, *and* one of the contexts includes definitions for both a noun and a verb, then that context **must** be listed on a line by itself, as in the above example. If the domain context has definitions of both a noun and a verb, then both contexts **must** be listed on separate lines.
|
107
|
+
|
108
|
+
##### Ordering of Multiple Contexts
|
109
|
+
|
110
|
+
When domain-context and technical-context definitions are supplied for the same term, the domain-context definition **must** always be first.
|
111
|
+
|
112
|
+
##### Meaning of Term Affected By Other Contexts
|
113
|
+
|
114
|
+
When a term is *primarily* relevant in one context, but changes in the understanding of another may affect that meaning, then the context phrase definition **must** include the word *Primarily*.
|
115
|
+
|
116
|
+
If more than two contexts have been defined within this glossary, then the other affecting contexts **must** also be listed, e.g., "*Primarily technical term; affected by pseudo-financial context.*" This serves as a warning that changes in the listed secondary context(s) could in future affect the primary definition of the term.
|
117
|
+
|
118
|
+
### Term Closely Tied to Other Term(s)
|
119
|
+
|
120
|
+
When a [Glossary](#glossary) term is closely conceptually dependent on one or more others, then that linkage **must** be indicated by a phrase equivalent to "*With reference to (other term),*" at the start of the dependent term's definition. For an example of this, see [Restricted](#restricted).
|
121
|
+
|
122
|
+
### Transitional Meaning of a Term
|
123
|
+
|
124
|
+
The Ubiquitous Language *will* evolve as the application it defines evolves over its lifetime. However, some terms are considered more likely to change than others, often because their definition and use are tightly bound to business policies and rules more than to intrinsic domain terminology. The definition of these terms **must** include the *either* word **currently** *or* the word **initially**, modified for correct grammar.
|
125
|
+
|
126
|
+
For an example of how this is used, see the definition of [Restricted](#restricted).
|
127
|
+
|
128
|
+
### Use of Term as Synonym (Cross-Reference)
|
129
|
+
|
130
|
+
When a term has a commonly-used synonym that has identical meaning to the original in all relevant contexts, then the full definition **must** be listed under what is, or is expected to be, the most commonly-used form of the term. Synonymous entries are permitted, as with the example shown below for [Authorisation Role](#authorisation-role), a presently-punctilious more specific term for [Role](#role).
|
131
|
+
|
132
|
+
### Link Formation
|
133
|
+
|
134
|
+
A Ubiquitous Language term definition, when linked to within hypertext markup such as [Component Service](#component-service) or email **must** be formed using a normal link (e.g., HTML `<a></a>` tag pair) with the properly-capitalised Ubiquitous Language term as the link text and a URL which references the specific definition on this page. For example, a reference on this page to the term [Component Services](#component-service) would be rendered in Markdown as `[Component Services](#component-service)`. Note that, as mentioned previously, the term *in the URL* is in the singular, and words are separated within the URL by hyphen (`-`) characters rather than underscores.
|
135
|
+
|
136
|
+
### Link on First Use in a Paragraph
|
137
|
+
|
138
|
+
The first use of a Ubiquitous Language term in a paragraph within hypertext markup such as specifications or email **should** link to its definition in this [Glossary](#glossary) **unless** that definition has been linked to in a "recent" paragraph within the markup *and* the term may reasonably be presumed to be understood in specific detail by the reader. An example of such a term might be Member Name. Document authors **should** exercise judicious restraint in considering exceptions, and readers who feel that unwarranted exceptions have been made **should** so inform the author.
|
139
|
+
|
140
|
+
### Optional Abbreviation After First Use in a Paragraph
|
141
|
+
|
142
|
+
Ubiquitous Language terms **may** be abbreviated after their first use in a paragraph. where the first use calls attention to the fact that future abbreviations are being used informally.
|
143
|
+
|
144
|
+
### Use Non-Abbreviated Form on First Use in Subsequent Paragraph
|
145
|
+
|
146
|
+
The Ubiquitous Language term previously abbreviated is normally fully expanded for its first use in any subsequent paragraph.
|
147
|
+
|
148
|
+
------
|
149
|
+
|
150
|
+
## The List
|
151
|
+
|
152
|
+
### Accept
|
153
|
+
|
154
|
+
*Primarily technical term.* The [`Accept` header](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) in the standard HTTP request headers is the basis of standard HTTP [content negotiation](#content-negotiiation). In our usage, [Clients](#client) making [Requests](#request) to a [Component Service](#component-service) **must** specify an `Accept` header with at least one content type specifying *either* that a specific [API Version](#api-version) of a specific Component Service is required (e.g., with a value of `vnd.example.apidemo.v4`) *or* that the latest available API Version of that Component Service is acceptable (with a comparable example of `vnd.example.apidemo`).
|
155
|
+
|
156
|
+
### API Version
|
157
|
+
|
158
|
+
*Technical term.* A sequential identifier, formed by the lower-case letter `v` and a positive ascending integer. Thus, `v1` would be the first version of a [Component Service](#component-service), `v2` would be a change (in API form or underlying implementation) from `v1`, and so on. Successive [API Versions](#api-version) increase in sequence, omitting only any API Versions which have been [Yanked](#yank).
|
159
|
+
|
160
|
+
In theory, any string can be used as an API Version identifier, though it traditionally revolves around numbers (e.g., '1', '2.7182.818.24'); a common convention is to prepend the lower-case letter `v` to such a number. All that is really required of an API Version identifier is that when two are compared lexically (as strings), the higher/later API Version has a higher/later sort order than the identifier it is being compared to.f
|
161
|
+
|
162
|
+
|
163
|
+
### API Version-Independent Delivery Application
|
164
|
+
|
165
|
+
*Technical term.* An executable application whose only purpose is to provide a non-API-Version-specific [Service Base URL](#service-base-url) for use by API clients, invoking the [Content Negotiation](#content-negotiation) middleware or equivalent which redirects (via HTTP 302, 307 or equivalent) to the [Primary Delivery Application](#primary-delivery-application) for a specific [API Version](#api-version).
|
166
|
+
|
167
|
+
### AVIDA
|
168
|
+
|
169
|
+
See [API Version-Independent Delivery Application](#api-version-independent-delivery-application).
|
170
|
+
|
171
|
+
### Client
|
172
|
+
|
173
|
+
An externally-developed and -maintained application or [Component Service](#component-service) which makes [Requests](#request) against the Component Service using this middleware in its [AVIDA](#avida).
|
174
|
+
|
175
|
+
### Component Service
|
176
|
+
|
177
|
+
*Technical term.* A Component Service is a self-contained, independently provisioned software construct that supplies part of the domain or supporting logic for a larger [Platform](#platform). Its only interface to or from other Component Services is via HTTP endpoints and JSON. Therefore, its definition, from the standpoint of any other component in the [System](#system), is expressed entirely in its interface, and no assumptions about implementation can or should be made. Evolution of a component, whether modifications to the API *or refactoring of the implementing code,* causes the [API Version](#api-version) to be incremented.
|
178
|
+
|
179
|
+
For a service to be a Component Service, it **must** meet each of the following criteria:
|
180
|
+
|
181
|
+
1. It **must** use HTTP as the sole means of initiating actions (via [Service Endpoints](#service-endpoint)) from outside code;
|
182
|
+
2. It **must** consist of an [API Version-Independent Delivery Application](#api-version-independent-delivery-application) and *one or more* [API Version](#api-version)-specific [Primary Delivery Application](#primary-delivery-application)s;
|
183
|
+
3. Its [AVIDA](#api-version-independent-delivery-application) **must** invoke [Content Negotiation](#content-negotiation) on each request for a [Service Endpoint](#service-endpoint);
|
184
|
+
4. It **must** [Register](#register) each API Version implemented with the service maintaining the [Repository](#repository) of currently-available Component Services, ordinarily using its [AVIDA](#api-version-independent-delivery-application)'s [Service Base URL](#service-base-url).
|
185
|
+
|
186
|
+
### Content Negotiation
|
187
|
+
|
188
|
+
*Technical term.* A standard feature of HTTP, normally used to select among multiple representations of the data being presented. This is commonly used for agreeing on the response's format (HTML, JSON, etc) and internationalisation (`en-gb`, `pt-br`, etc) to use.
|
189
|
+
|
190
|
+
Used by an application/[Platform](#platform) to select among (possibly) multiple supported [API Versions](#api-version) of a specific [Component Service](#component-service), with sensible default handling. For example, given a Platform named `acme` and an `ApiDemo` Component Service that supports API Versions `1` and `2`, a request which specifies a value for the `Accept` HTTP header of `application/vnd.acme.apidemo.v1` will be served by the implementation of API Version `1`; a value of `application/vnd.acme.apidemo`, `application/*`, or `*/*` will be handled by the most recent API Version `2`, exactly as if it had been requested by a value of `application/vnd.acme.apidemo.v2`.
|
191
|
+
|
192
|
+
### Deprecate
|
193
|
+
|
194
|
+
*Technical term.* To serve notice that a specific [API Version](api-version) of a specific [Component Service](#component-service) **should not** be used since it will be removed from the [Platform](#platform)'s ecosystem at some future time. Initiating operation of a Component Service with one or more Deprecated API Versions, and/or accessing a [Service Endpoint](#service-endpoint) using a Deprecated API Version, **must** generate diagnostic messages using the customary means for operational monitoring, e.g., logging.
|
195
|
+
|
196
|
+
### Endpoint
|
197
|
+
|
198
|
+
See [Service Endpoint](#service-endpoint).
|
199
|
+
|
200
|
+
### Entity
|
201
|
+
|
202
|
+
*Technical term.* An Entity, is an object that is not defined by its attributes, but each instance has a distinct identity and thread of continuity (flow of internal state over its lifecycle). Contrast [Value Object](#value-object).
|
203
|
+
|
204
|
+
The term Entity is sometimes casually used for an immutable piece of data that is more properly classed as a Value Object. If it is exclusively used for directly-read attribute values or simple derivations there from ("full name" as a combination of "given name" and "family name" is the classic example), then it *is* a Value Object and **must** be classed as such. Conversely, an Entity should modify its state through actions performed by methods, and minimise "raw" access to data (see *[Tell, Don't Ask](https://pragprog.com/articles/tell-dont-ask)*). A collaborator *asks* a Value Object for something; it *tells* an Entity to do something with itself. (An object that does something with objects *other than* itself is performing a service and is not, strictly speaking, an entity.)
|
205
|
+
|
206
|
+
*Non-normative supplemental information follows.*
|
207
|
+
|
208
|
+
The non-normative supplemental information for [Value Object](#value-object) is relevant here. *Unless* an object delivers significant value by having a mutable state over time, *and* is constantly maintained as a single Entity instance throughout by collaborating code, a Value Object **should** be used instead.
|
209
|
+
|
210
|
+
### Glossary
|
211
|
+
|
212
|
+
*Primarily domain term.* The document defining the [Ubiquitous Language](http://blog.carbonfive.com/2016/10/04/ubiquitous-language-the-joy-of-naming/) in use for and by a particular application or [Platform](#platform).
|
213
|
+
|
214
|
+
### PDA
|
215
|
+
|
216
|
+
See [Primary Delivery Application](#primary-delivery-application).
|
217
|
+
|
218
|
+
### Platform
|
219
|
+
|
220
|
+
*Technical term.* Refers to an aggregate of multiple applications and/or [Component Services](#component-service) which, collectively, provide a conceptually unified service or product to users (as opposed to [Clients](#client)). For example, Facebook is a platform; it is not a single, monolithic application.
|
221
|
+
|
222
|
+
### Primary Delivery Application
|
223
|
+
|
224
|
+
*Technical term.* The executable program/process implementing a specific [API Version](#api-version) of a specific [Component Service](#component-service), which **should** be redirected to by the [API Version-Independent Delivery Application](#api-version-independent-delivery-application). It **must** provide the API implemented by its [Service Endpoints](#service-endpoint) via HTTP.
|
225
|
+
|
226
|
+
### Register
|
227
|
+
|
228
|
+
*Technical term.* The process of making another [Component Service](#component-service) known to and addressable based upon information retrieved from the [Repository](#repository) used by this Gem's middleware.
|
229
|
+
|
230
|
+
### Repository
|
231
|
+
|
232
|
+
*Technical term.* A Repository, in general, intermediates between application-level logic and what is presumed to be (and generally treated as) persistent storage of structured data. Our middleware, specifically `ServiceComponentDescriber`, queries a Repository for information about the [Component Service](#component-service) implemented by the application hosting the middleware. How and whether that data is actually persisted, or how and how often that data is updated to match then-current conditions, are details unknown to and, practically speaking, irrelevant to, the middleware and its hosting application.
|
233
|
+
|
234
|
+
### Request
|
235
|
+
|
236
|
+
*Technical term.* Making an HTTP request, and acting upon its response, is the sole means of interacting with a [Component Service](#component-service) as that concept is defined here. The Request **must** specify the Component Service addressed by the request, and **must** *either* specify a specific [API Version](#api-version) of that Service *or* specify that the latest (most recent, highest version identifier) API Version is acceptable.
|
237
|
+
|
238
|
+
### Service
|
239
|
+
|
240
|
+
*Technical term.* See [Component Service](#component-service).
|
241
|
+
|
242
|
+
### Service Base URL
|
243
|
+
|
244
|
+
*Technical term.* The common base of all [Service Endpoints](#service-endpoint) for a given [Component Service](#component-service). An [AVIDA](#avida) and a corresponding [API Version](#api-version)-specific [Primary Delivery Application](#primary-delivery-application) each have unique Service Base URLs.
|
245
|
+
|
246
|
+
The URL to access a specific [Service Endpoint](#service-endpoint) is formed by appending the path information and query parameters, if any, to the Service Base URL for the Component Service being addressed.
|
247
|
+
|
248
|
+
### Service Description
|
249
|
+
|
250
|
+
*Primarily technical term.* A string which describes the purpose and/or function of a [Service Component](#service-component) with greater semantic meaning than a [Service Name](#service-name). Like a Service Name, this **must** also be unique within the [System](#system). It is primarily for the benefit of UI mechanisms which describe Service Component information in an operationally relevant context.
|
251
|
+
|
252
|
+
### Service Endpoint
|
253
|
+
|
254
|
+
*Technical term.* Uniquely identified by combining a [Service Base URL](#service-base-url) and [Action Name](#action-name), this, after making use of the Content Negotiation [Utility Component](#utility-component), invokes a function or method in a specific API Version of the implementing code responsible for performing a useful action or accessing a useful resource as part of a [Component Service](#component-service).
|
255
|
+
|
256
|
+
### Service Name
|
257
|
+
|
258
|
+
*Technical term.* A short string used to uniquely identify a [Component Service](#component-service) internally within the Conversagence ecosystem. This is distinct from the [Service Description](#service-description). It **must** be unique within the [System](#system).
|
259
|
+
|
260
|
+
### Specification
|
261
|
+
|
262
|
+
*Primarily technical term.* In the Ubiquitous Language, specifically refers to the specification of the API and associated functionality of a specific [Component Service](#component-service), typically defined in a single hypertext document similar to this one.
|
263
|
+
|
264
|
+
Specifications are expected to converge on a common set of formatting and content standards that **must** be documented here after multiple source samples establish those standards through usage and review between Stakeholders.
|
265
|
+
|
266
|
+
### Stakeholder
|
267
|
+
|
268
|
+
*Domain term.* An individual or cohesive collection of individuals participating in the continuing development, including supporting business operations, of the Conversagence Project. These include, but are not limited to, [Members](#member), developers, operational specialists, domain experts, and investors. Each of these, and others, have a "stake" in the success of the Project and the organisation, and each are essential contributors to that success.
|
269
|
+
|
270
|
+
### Target Service
|
271
|
+
|
272
|
+
*Technical term.* Used by a Utility Component's code or Specification to refer to the Component Service on whose behalf it is performing work. This term's first use in a specific context **must** make clear whether this term refers to *any* API Version of the Component Service being targeted rather than to a *single* API Version as is the intended default usage of the term.
|
273
|
+
|
274
|
+
### Value Object
|
275
|
+
|
276
|
+
*Technical term.* Value Objects have no identity or flow of internal state (they are immutable), and are defined by their attribute values. Examples are colours (which are expressed using values such as sRGB and CMYK), geolocations (latitude and longitude values), and so on. Two value objects may be compared on the basis of their attributes (the location for Springfield is not the same as the location for Singapore), but assignment normally does not make sense in context (e.g., changing the values for the "location of Singapore" object to those of the "location of Springfield" object and expecting to continue to see the two as having distinct meaning). Contrast [Entity](#entity).
|
277
|
+
|
278
|
+
*Non-normative supplemental information follows.*
|
279
|
+
|
280
|
+
*In general,* immutable value objects are to be preferred in use to mutable entities; when an "updated" object is needed, instantiate a new object and delete (or garbage-collect) the old one. Maintaining and preserving mutable state is a fraught enterprise that sucks resources and is a traditionally rich source of defects.
|
281
|
+
|
282
|
+
### Yank
|
283
|
+
|
284
|
+
*Technical term.* To Yank a specific [API Version](#api-version) of a specific [Specification](#specification) is to remove it from possible use by any [Component Service](#component-service). This **should** normally be done after the API Version in question has been [Deprecated](#deprecate) for a period of time, generally due to later changes in the API obsoleting the to-be-Yanked API Version.
|
285
|
+
|
286
|
+
Yanking a specific [API Version](#api-version) **may** also be necessary as part of an urgent security response, when vulnerabilities or other critical failures have been discovered in the version in question.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './encoded_api_version_data'
|
4
|
+
require_relative './input_env'
|
5
|
+
require_relative './input_is_invalid'
|
6
|
+
require_relative './match_header_against_api_versions'
|
7
|
+
require_relative './report_no_matching_version'
|
8
|
+
|
9
|
+
# All(?) Rack code is namespaced within this module.
|
10
|
+
module Rack
|
11
|
+
# Module includes our middleware components for managing service API versions.
|
12
|
+
module ServiceApiVersioning
|
13
|
+
# Select API Version of Component Service based on HTTP `Accept` header.
|
14
|
+
class AcceptContentTypeSelector
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
@env = nil
|
18
|
+
@input = nil
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
@env = env
|
24
|
+
@input = InputEnv.new env
|
25
|
+
match_request_from_header
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :app, :env, :input
|
31
|
+
|
32
|
+
def add_component_api_version_data(version)
|
33
|
+
env['COMPONENT_API_VERSION_DATA'] = encoded_data_for(version)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def api_versions
|
38
|
+
input.data[:api_versions]
|
39
|
+
end
|
40
|
+
|
41
|
+
def encoded_data_for(api_version)
|
42
|
+
EncodedApiVersionData.call(api_version: api_version, data: input.data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def if_component_apis_defined(&_)
|
46
|
+
InputIsInvalid.call(input) || yield
|
47
|
+
end
|
48
|
+
|
49
|
+
def if_requested_api_version_found
|
50
|
+
best_version = specified_api_version
|
51
|
+
return report_no_matching_api_versions unless best_version
|
52
|
+
yield best_version
|
53
|
+
end
|
54
|
+
|
55
|
+
def match_request_from_header
|
56
|
+
if_component_apis_defined do
|
57
|
+
if_requested_api_version_found do |matching_version|
|
58
|
+
use_api_version(matching_version)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def report_no_matching_api_versions
|
64
|
+
ReportNoMatchingVersion.call api_versions: api_versions
|
65
|
+
end
|
66
|
+
|
67
|
+
def specified_api_version
|
68
|
+
MatchHeaderAgainstApiVersions.call(accept_header: env['HTTP_ACCEPT'],
|
69
|
+
api_versions: api_versions)
|
70
|
+
end
|
71
|
+
|
72
|
+
def use_api_version(matching_version)
|
73
|
+
add_component_api_version_data(matching_version)
|
74
|
+
app.call env
|
75
|
+
end
|
76
|
+
end # class AcceptContentTypeSelector
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/response'
|
5
|
+
|
6
|
+
require_relative './build_redirect_uri_from_env'
|
7
|
+
|
8
|
+
# All(?) Rack code is namespaced within this module.
|
9
|
+
module Rack
|
10
|
+
# Module includes our middleware components for managing service API versions.
|
11
|
+
module ServiceApiVersioning
|
12
|
+
# Returns an HTTP 302 response with cleaned-up environment and `Location`
|
13
|
+
# header.
|
14
|
+
class ApiVersionRedirector
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
@env = nil
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
@env = env
|
23
|
+
response
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
DEFAULT_STATUS = 307
|
29
|
+
private_constant :DEFAULT_STATUS
|
30
|
+
|
31
|
+
attr_reader :app, :env
|
32
|
+
|
33
|
+
def api_version
|
34
|
+
api_version_data[:api_version]
|
35
|
+
end
|
36
|
+
|
37
|
+
def api_version_data
|
38
|
+
JSON.parse(env['COMPONENT_API_VERSION_DATA'], symbolize_names: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def body
|
42
|
+
'Please resend the request to ' \
|
43
|
+
"<a href=\"#{location}\">#{location}</a>" \
|
44
|
+
' without caching it.'
|
45
|
+
end
|
46
|
+
|
47
|
+
def headers
|
48
|
+
{ 'API-Version' => api_version, 'Location' => location }
|
49
|
+
end
|
50
|
+
|
51
|
+
def location
|
52
|
+
BuildRedirectUriFromEnv.call(env)
|
53
|
+
end
|
54
|
+
|
55
|
+
def response
|
56
|
+
Rack::Response.new(body, DEFAULT_STATUS, headers).finish
|
57
|
+
end
|
58
|
+
end # class Rack::ServiceApiVersioning::ApiVersionRedirector
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/response'
|
5
|
+
|
6
|
+
# All(?) Rack code is namespaced within this module.
|
7
|
+
module Rack
|
8
|
+
# Module includes our middleware components for managing service API versions.
|
9
|
+
module ServiceApiVersioning
|
10
|
+
# Build redirect URI from original request URI and API Version SBU.
|
11
|
+
class BuildRedirectLocationUri
|
12
|
+
def self.call(api_version_base_uri:, request_uri:)
|
13
|
+
new(api_version_base_uri, request_uri).call
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
update_path
|
18
|
+
update_query
|
19
|
+
uri_for_redirect
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def initialize(api_version_base_uri, request_uri)
|
25
|
+
@new_base_uri_parts = api_version_base_uri.to_hash
|
26
|
+
@request_uri = request_uri
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :new_base_uri_parts, :request_uri
|
33
|
+
|
34
|
+
def combined_path_str
|
35
|
+
new_base_uri_parts[:path] + request_uri.path
|
36
|
+
end
|
37
|
+
|
38
|
+
def path_str
|
39
|
+
combined_path_str.sub('//', '/')
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_path
|
43
|
+
new_base_uri_parts[:path] = path_str
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_query
|
47
|
+
new_base_uri_parts[:query] = request_uri.query
|
48
|
+
end
|
49
|
+
|
50
|
+
def uri_for_redirect
|
51
|
+
Addressable::URI.new(new_base_uri_parts)
|
52
|
+
end
|
53
|
+
end # class Rack::ServiceApiVersioning::BuildRedirectLocationUri
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'addressable'
|
4
|
+
require 'rack'
|
5
|
+
require 'rack/response'
|
6
|
+
|
7
|
+
require_relative './build_redirect_location_uri'
|
8
|
+
|
9
|
+
# All(?) Rack code is namespaced within this module.
|
10
|
+
module Rack
|
11
|
+
# Module includes our middleware components for managing service API versions.
|
12
|
+
module ServiceApiVersioning
|
13
|
+
# Build redirect URI from data in `env`
|
14
|
+
class BuildRedirectUriFromEnv
|
15
|
+
def self.call(env)
|
16
|
+
new(env).call
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
location_uri_from(request_uri).to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def initialize(env)
|
26
|
+
@env = env
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :env
|
33
|
+
|
34
|
+
def api_version_base_uri
|
35
|
+
Addressable::URI.parse(api_version_data[:base_url])
|
36
|
+
end
|
37
|
+
|
38
|
+
def api_version_data
|
39
|
+
JSON.parse(env['COMPONENT_API_VERSION_DATA'], symbolize_names: true)
|
40
|
+
end
|
41
|
+
|
42
|
+
def location_uri_from(request_uri)
|
43
|
+
params = { api_version_base_uri: api_version_base_uri,
|
44
|
+
request_uri: request_uri }
|
45
|
+
BuildRedirectLocationUri.call params
|
46
|
+
end
|
47
|
+
|
48
|
+
def request_uri
|
49
|
+
request_str = Rack::Request.new(env).url
|
50
|
+
Addressable::URI.parse(request_str)
|
51
|
+
end
|
52
|
+
end # class BuildRedirectUriFromEnv
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
# All(?) Rack code is namespaced within this module.
|
6
|
+
module Rack
|
7
|
+
# Module includes our middleware components for managing service API versions.
|
8
|
+
module ServiceApiVersioning
|
9
|
+
# Builds an API Version data hash and encodes as JSON, for injection into a
|
10
|
+
# Rack environment, normally with the key "COMPONENT_API_VERSION_DATA".
|
11
|
+
class EncodedApiVersionData
|
12
|
+
# Unpacks relevant attributes from passed-in input data
|
13
|
+
class InputData < Dry::Struct::Value
|
14
|
+
attribute :api_version, Types::Strict::Symbol
|
15
|
+
attribute :input_data, Types::Hash
|
16
|
+
|
17
|
+
def base_url
|
18
|
+
version_data[:base_url]
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
input_data[:name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def vendor_org
|
26
|
+
content_type_parts[VENDOR_ORG_INDEX]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Index 0 will be `application/vnd`; index 1 the org name (eg, `acme`)
|
32
|
+
VENDOR_ORG_INDEX = 1
|
33
|
+
private_constant :VENDOR_ORG_INDEX
|
34
|
+
|
35
|
+
def content_type_parts
|
36
|
+
version_data[:content_type].split('.')
|
37
|
+
end
|
38
|
+
|
39
|
+
def version_data
|
40
|
+
input_data[:api_versions][api_version]
|
41
|
+
end
|
42
|
+
end # class EncodedApiVersionData::InputData
|
43
|
+
end # class EncodedApiVersionData
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
# All(?) Rack code is namespaced within this module.
|
6
|
+
module Rack
|
7
|
+
# Module includes our middleware components for managing service API versions.
|
8
|
+
module ServiceApiVersioning
|
9
|
+
# Exception wrapper class for invalid return data from
|
10
|
+
# `EncodedApiVersionData#version_data` due to a bad SBU being specified.
|
11
|
+
# Not nested within that class to avoid leaking unnecessary implementation
|
12
|
+
# detail, *even though* this class is (presently) only used by that class.
|
13
|
+
class InvalidBaseUrlError < RuntimeError
|
14
|
+
def initialize(base_url, original_error)
|
15
|
+
@original_error = original_error
|
16
|
+
super "Invalidly formatted base URL: #{base_url}"
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :original_error
|
20
|
+
end # class InvalidBaseUrlError
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
# All(?) Rack code is namespaced within this module.
|
6
|
+
module Rack
|
7
|
+
# Module includes our middleware components for managing service API versions.
|
8
|
+
module ServiceApiVersioning
|
9
|
+
# Builds an API Version data hash and encodes as JSON, for injection into a
|
10
|
+
# Rack environment, normally with the key "COMPONENT_API_VERSION_DATA".
|
11
|
+
class EncodedApiVersionData
|
12
|
+
# Immutable, structured data type for returned version data.
|
13
|
+
class ReturnData < Dry::Struct::Value
|
14
|
+
SBU_FMT = %r{\A\w+?://.+?/\z}
|
15
|
+
private_constant :SBU_FMT
|
16
|
+
|
17
|
+
constructor_type :strict_with_defaults
|
18
|
+
|
19
|
+
attribute :api_version, Types::Coercible::String
|
20
|
+
attribute :base_url, Types::Strict::String.constrained(format: SBU_FMT)
|
21
|
+
attribute :name, Types::Strict::String
|
22
|
+
attribute :deprecated, Types::Strict::Bool.default(false)
|
23
|
+
attribute :restricted, Types::Strict::Bool.default(false)
|
24
|
+
attribute :vendor_org, Types::Strict::String
|
25
|
+
|
26
|
+
def content_type
|
27
|
+
content_parts.join('.') + '+json'
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
super.merge(content_type: content_type)
|
32
|
+
.reject { |key, _| key == :vendor_org }
|
33
|
+
end
|
34
|
+
alias to_h to_hash
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def content_parts
|
39
|
+
['application/vnd', vendor_org, name, api_version.to_s]
|
40
|
+
end
|
41
|
+
end # class EncodedApiVersionData::ReturnData
|
42
|
+
end # class EncodedApiVersionData
|
43
|
+
end
|
44
|
+
end
|