Created
September 20, 2021 13:00
-
-
Save adam-arold/21023f8b6ea07a45557f251002dabc40 to your computer and use it in GitHub Desktop.
ContentGateway DEMO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
import * as E from "fp-ts/Either"; | |
import * as t from "io-ts"; | |
/** | |
* Contains the necessary fields that are required to uniquely | |
* identify a specific event. | |
* The generic type parameter is necessary for type safety. | |
*/ | |
type Key<T> = { | |
version: string; | |
namespace: string; | |
name: string; | |
} | |
/** | |
* An envelope like this is usually needed when serialization is involved | |
* (which will be the case with us.) | |
*/ | |
type Payload<T> = { | |
event: T | |
} & Key<T>; | |
/** | |
* I've used io-ts here, but any other library would be OK. | |
*/ | |
type EventType<T> = { | |
eventType: t.Type<T>; | |
} & Key<T>; | |
/** | |
* Creates a unique key from an [[EventType]]. | |
*/ | |
const keyToString: <T>(key: Key<T>) => string = ({namespace, name, version}) => `${namespace}.${name}.${version}`; | |
/** | |
* Contains the functionality offered by the content gateway. | |
*/ | |
type ContentGateway = { | |
register: <T> (eventType: EventType<T>) => E.Either<Error, void>; | |
send: <T> (payload: Payload<T>) => E.Either<Error, void>; | |
listEventTypes: () => EventType<any>[]; | |
getEventType: <T> (key: Key<T>) => E.Either<Error, EventType<T>>; | |
getEvents <T> (key: Key<T>): T[]; | |
} | |
/** | |
* The SDK is responsible for providing an easy-to-use interface for downstream | |
* systems. | |
* It also hides the complexity of the underlying integration platform. | |
*/ | |
type SDK = { | |
register: <T> (eventType: EventType<T>) => E.Either<Error, void>; | |
send: <T> (event: Payload<T>) => E.Either<Error, void>; | |
} | |
/** | |
* This repository maintains the type information for each event type. | |
*/ | |
type CatalogRepository = { | |
save: <T> (eventType: EventType<T>) => E.Either<Error, void>; | |
findEventTypes: () => Array<EventType<any>>; | |
findByKey: <T> (key: Key<T>) => E.Either<Error, EventType<T>>; | |
} | |
/** | |
* We store the actual events here (that were sent from the downstream systems). | |
*/ | |
type EventRepository = { | |
save: <T> (payload: Payload<T>) => E.Either<Error, void>; | |
getEvents: <T> (key: Key<T>) => T[]; | |
} | |
// 🟢 demo only 😃 | |
const catalogItems: Map<string, EventType<any>> = new Map(); | |
const events: Array<Payload<any>> = []; | |
/** | |
* I usually prefer a functional style (currying) and this is a factory function for creating | |
* a [[CatalogRepository]] that uses a [[Map]]. | |
*/ | |
const createInMemoryCatalogRepository = (ci: Map<string, EventType<any>>): CatalogRepository =>{ | |
return { | |
save: <T> (eventType: EventType<T>) => { | |
const key = keyToString(eventType); | |
if(ci.has(key)) { | |
return E.left(Error(`There is already a catalog item with the given key ${key}`)); | |
} else { | |
ci.set(key, eventType); | |
return E.right(undefined); | |
} | |
}, | |
findEventTypes: () => Array.from(ci.values()), | |
findByKey: <T> (key: Key<T>): E.Either<Error, EventType<T>> => { | |
const keyStr = keyToString(key); | |
if(ci.has(keyStr)) { | |
return E.right(ci.get(keyStr)as EventType<T>); | |
} else { | |
return E.left(Error(`There is no catalog item with the given key ${key}`)); | |
} | |
} | |
}; | |
}; | |
/** | |
* Same as above, but for [[Payload]]s. | |
*/ | |
const createInMemoryEventRepository = (e: Array<Payload<any>>): EventRepository => { | |
return { | |
save: <T> (payload: Payload<T>) => { | |
e.push(payload); | |
return E.right(undefined); | |
}, | |
getEvents: <T> (key: Key<T>): Array<T> => { | |
return e | |
.filter(payload => keyToString(payload) === keyToString(key)) | |
.map(payload => payload.event) as Array<T>; | |
} | |
}; | |
}; | |
const inMemoryCatalogRepository = createInMemoryCatalogRepository(catalogItems); | |
const inMemoryEventRepository = createInMemoryEventRepository(events); | |
const registerInMemory = <T> (eventType: EventType<T>) => inMemoryCatalogRepository.save(eventType); | |
const sendInMemory = <T> (event: Payload<T>) => inMemoryEventRepository.save(event); | |
/** | |
* This also uses in-memory contstructs for now. | |
* For the MVP we can add mongo/postgres. | |
*/ | |
const contentGateway: ContentGateway = { | |
register: registerInMemory, | |
send: sendInMemory, | |
listEventTypes: inMemoryCatalogRepository.findEventTypes, | |
getEventType: inMemoryCatalogRepository.findByKey, | |
getEvents: <T> (key: Key<T>) => inMemoryEventRepository.getEvents<T>(key) | |
}; | |
/** | |
* Right now there is no remoting, the SDK just calls the gateway as-is | |
*/ | |
const sdk: SDK = { | |
register: contentGateway.register, | |
send: contentGateway.send | |
}; | |
type POAP = { | |
id: number; | |
eventType: string; | |
imageUrl: string; | |
} | |
const poapV1Key = { | |
"version": "v1", | |
"namespace": "poap", | |
"name": "poap", | |
}; | |
const registrationResult = sdk.register<POAP>({ | |
eventType: t.type({ | |
id: t.number, | |
eventType: t.string, | |
imageUrl: t.string | |
}), ...poapV1Key | |
}); | |
const sendResult = sdk.send<POAP>({ | |
event: {id: 1, | |
eventType: "test", | |
imageUrl: "http://example.com/image.png" | |
}, | |
...poapV1Key | |
}); | |
console.log(registrationResult); | |
console.log("=========================="); | |
console.log(sendResult); | |
console.log("=========================="); | |
console.log(contentGateway.listEventTypes()); | |
console.log("=========================="); | |
console.log(contentGateway.getEventType(poapV1Key)); | |
console.log("=========================="); | |
console.log(contentGateway.getEvents(poapV1Key)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment