Skip to content

Instantly share code, notes, and snippets.

@adam-arold
Created September 20, 2021 13:00
Show Gist options
  • Save adam-arold/21023f8b6ea07a45557f251002dabc40 to your computer and use it in GitHub Desktop.
Save adam-arold/21023f8b6ea07a45557f251002dabc40 to your computer and use it in GitHub Desktop.
ContentGateway DEMO
/* 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