[Proposal] Encode constant values in conjure file
Even though all state encoding should go through the current type-able constructs, there are cases where a constant value becomes part of an API. This can happen when interacting with a tertiary API which accepts dynamic keys, which are fixed for the service. For example, if a service registers with a notification system, the channel it registers might be constant, but not encodable in any currently-existing way (imagine e.g. a channel name that takes / or :; this cannot be encoded in the current enum specification), but should still be documented and accessible.
Proposal:
In the definitions field, add a constants subkey (see the snippet at the end for a sample conjure specification). Constants have a type and a value. The type can potentially be any locally-valid conjure type. Until we have concrete requirements for more complex types, the initially supported set of values should be restricted to primitives. The value would be a valid yaml-encoding of the conjure value. As in, it should be possible to deserialize the value of the value key to generate the constant value (though this isn't the recommended implementation for code generators).
The values must be validated (i.e. a invalid constant value must be a compile error, not a runtime error) according to the Conjure spec.
Constants also have a package & wrapper field, which correspond to the package & compilation unit the constants will be attached to. The $package.$wrapper name must be different from any Conjure object (i.e. there will be no mixed constant & object output). These can both be set through file defaults as well (default-package & default-wrapper).
Trade-offs/Risks
- Tertiary service APIs can force the usage of constants. This enables the formal encoding and documentation, instead of e.g. adding the constant in the doc string.
- Encoding of non-primitive constants not necessarily a good idea. This should be held off until concrete uses are found.
- This can be misused instead of fields/enums. For example, services might build endpoints of a
map<string, any>type, with a set of assorted constant keys. This is an anti-pattern, and should be strongly discouraged. - This can't solve the problem in cases where the decision is dynamic. In the notification example, imagine the target resource channel can be computed using a lambda (e.g. if there's a notification channel per dataset). There is no way to encode this (nor should there be), limiting the usefulness of this extension.
Sample
types:
definitions:
constants:
default-package: com.example.foo
default-wrapper: Constants
ChannelA:
docs: "Notification channel for high-priority service notifications"
type: string
value: "my:channel"
ReadOperation:
docs: "Operation used to enforce read checks"
type: string
value: "my-service:read"
- [ ] Add concrete examples
- [ ] Refine with generator proposal
Alternatives:
Enums
The main limitation there is the strong limitations on enum naming. Event/stream/channel names often (by convention or by technical requirement) have a name that doesn't match that format (e.g. contains -, : or / as part of the value). Another very specific example that cannot be encoded using enums are resource identifiers.
Service constants
Expose a service with known values (e.g. identifying constants by an enum), which can be queried. Pros: already supported, allows for arbitrary complexity, has an upgrade story (as in, if constants need to be changed). Cons: incurs overheads (particularly for FEs that can't be as aggressive about caching), simple operations suddenly involve network calls.
Did you find a representative list of use cases for constants in our APIs, @raiju ? (Trying to tease out whether "do nothing" would be an acceptable path forward.)