# Message Specification ## Project Creation Data Properties on data service: ``` { project_id: str name: str slug: str repositories: list[str] visibility: “public”|”private” description: optional[str] created_by: user creation_date: datetime members:list[user] } ``` ## General Message Shell (based on cloud events) ``` { id: redis id headers:{ source: “renku-data-service” type: “renku.project.created” datacontenttype: “application/avro+json”|”application/avro+binary” schemaversion: "v1" requestid: "p33imb6qfnr817p9ltl8nqn38btvbibz" //follows the X-REQUEST-ID header on gateway time: datetime } payload:{ ...payload } } ``` Not used: - specversion: major.minor, required in cloud events, not sure we need it. * The version of the CloudEvents specification which the event uses. This enables the interpretation of the context. Compliant event producers MUST use a value of 1.0 when referring to this version of the specification.* - subject: *This describes the subject of the event in the context of the event producer (identified by source). In publish-subscribe scenarios, a subscriber will typically subscribe to events emitted by a source, but the source identifier alone might not be sufficient as a qualifier for any specific event if the source context has internal sub-structure.* - dataschema: *Identifies the schema that data adheres to. Incompatible changes to the schema SHOULD be reflected by a different URI* ## AsyncAPI For channels, in rabbitmq we could have had routing keys like `project.<id>.created` and subscribers could listen for e.g. `project.1.*` or `project.*`. In Redis streams this isn't feasible, as there's no pattern matching. So we need generic streams and consumers need to potentially do pattern matching. We should also add consumer groups for search. We could have streams per entity or per entity+event-type. For now I assume entity+event-type. ``` # in file asyncapi/v1/project.yaml asyncapi: 3.0.0 info: title: Project Events version: 0.0.1 servers: redis: url: renku-redis protocol: redis description: Renku Redis Instance channels: project.created: publish: messages: projectCreated: schemaFormat: "application/vnd.apache.avro;version=1.9.0" payload: type: object properties: payload: - $ref: '/project/v1/avro/created.avsc#/ProjectCreated' traits: - $ref: '#/components/messageTraits/headers' components: messageTraits: headers: payload: type: object properties: id: type: string headers: - $ref: '/common/v1/avro/headers.avsc#/Header' --- # on file common/v1/avro/headers.avsc { "type":"record", "name":"Header", "fields":[ { "name": "source", "type":"string" }, { "name": "type", "type":"string" }, { "name": "datacontenttype", "type":"enum", "symbols":["application/avro+json","application/avro+binary"] }, { "name":"schemaversion", "type":"string" }, { "name":"time", "type":"int", "logicalType":"timestamp-millis" }, { "name":"requestid", "type":"string" } ] } --- # in file /project/v1/avro/created.avsc { "type":"record", "name":"ProjectCreated", "fields":[ { "name":"name", "type":"string" }, { "name":"slug", "type":"string" }, { "name":"repositories", "type":"array", "items": "string", "default":[] }, { "name":"visibility", "type":"enum", "symbols":["public","private"] }, { "name":"description", "type":["null", "string"] }, { "name":"created_by", "type":"string" }, { "name":"creation_date", "type":"int", "logicalType":"timestamp-millis" }, { "name":"members", "type": "array", "items":"string", "default":[] } ] } ``` - do we need servers section? It seems a bit useless to me. (we'll leave it in with just renku-redis) - should only payload be avro or whole message (headers is avro-json, payload is avro json or binary) - one downside of avro is that it doesn't allow merging schemas (to my knowledge), so we couldn't have the common properties in an avsc file and refer to them from other avsc. we could define an avro type like "Headers" and refer to it in other schemas, but unsure if common clients handle multiple schemas ## Folder Layout there's two top level folders, one for AsyncAPI, one for Avro Schemas, with the former referencing the latter. ``` . ├── project/ │ └── v1/ │ ├── asyncapi.yaml │ └── events/ │ ├── created.avsc │ ├── updated.avsc │ └── deleted.avsc ├── user/ │ └── v1/ │ ├── asyncapi.yaml │ └── events/ │ ├── created.avsc │ └── deleted.avsc └── common/ └── v1/ └── headers.avsc ``` How to do versioning isn't really clear. I went with the simple option of only changing the version with breaking changes and otherwise just updating the existing file, as avro should handle non-breaking changes. We should add some CI to check that no breaking change sneaks in.