Skip to content

Instantly share code, notes, and snippets.

@freddi301
Created September 19, 2017 15:12
Show Gist options
  • Save freddi301/5219bd4a190227e812b191931c759195 to your computer and use it in GitHub Desktop.
Save freddi301/5219bd4a190227e812b191931c759195 to your computer and use it in GitHub Desktop.
version aware programming proof of concept
// @flow
/// as first we declare the contract our program will have with external world
type v1Api = {
getPerson(id: string): v1Person,
addPerson(person: v1Person): void
};
type v1Person = { id: string, name: string, age: number };
/// let's see what happens if we add some capability to our api
type v2Api = {
getPerson(id: string): v2Person,
addPerson(person: v2Person): void,
allPerson(): Array<v2Person>
};
type v2Person = v1Person;
/// let's leverage the typechecker (compiler in compiled typed languages) to check if our api is retrocompatible
/// trying to assign an instance of the new version to the old will do the trick 1*
declare var v1ApiControlIntance: v1Api;
declare var v2ApiControlIntance: v2Api;
v1ApiControlIntance = v2ApiControlIntance;
/// let's break the api and see how the compiler can be useful detecting the incompatibilities
type v3Api = {
getPerson(id: string): v3Person,
addPerson(person: v3Person): void
};
type v3Person = v2Person; // 2*
declare var v3ApiControlInstance: v3Api;
/// you can see here how the compiler complains
// $ExpectError - erase this line to see the error
v2ApiControlIntance = v3ApiControlInstance; // 'property `allPerson` (Property not found in object type)'
/// lets'see how we can retain runtime data reloading
const v1ApiInstance: v1Api & { people: { [key: string]: v1Person } } = {
people: {},
getPerson(id: string): v1Person {
return this.people[id];
},
addPerson(person: v1Person): void {
this.people[person.id] = person;
}
};
const v2ApiInstance: v2Api & { people: { [key: string]: v2Person } } = {
people: Object.assign({}, v1ApiInstance.people), // migration here 3*
getPerson(id: string): v1Person {
return this.people[id];
},
addPerson(person: v1Person): void {
this.people[person.id] = person;
},
allPerson(): Array<v2Person> {
return this.people;
}
};
// example workflow
let entryPoint: any; // 4*
entryPoint = v1ApiInstance; // deployed version 1
(entryPoint: v1Api).addPerson({ id: '1', name: 'fred', age: 23 }); // using version 1
entryPoint = v2ApiInstance; // deployed version 2
(entryPoint: v2Api).allPerson(); // using version 2
/// let's see how we can achieve safe data migration
// first we need our v3 api instance
const v3ApiInstance: v3Api & { people: { [key: string]: v3Person } } = {
people: {},
getPerson(id: string): v3Person {
return this.people[id];
},
addPerson(person: v3Person): void {
this.people[person.id] = person;
}
};
// then declare v4 api type
type v4Person = { id: string, name: string, age: number, birth: Date };
type v4Api = {
getPerson(id: string): v4Person,
// $ExpectError
addPerson(person: v4Person): void // the error is reported here because v3Person is not compatible with v4Person
};
declare var v4ApiControlInstance: v4Api;
// the error is reported on the v4Api declaration
v3ApiControlInstance = v4ApiControlInstance;
const v4ApiInstance: v4Api & { people: { [key: string]: v4Person } } = {
// here is our simple migration method that iterates our previous repository and populates the new one
people: Object.keys(v3ApiInstance.people).reduce((people: { [key: string]: v4Person }, id) => {
people[id] = v3PersonTov4Person(v3ApiInstance.people[id]);
return people;
}, {}),
getPerson(id: string): v4Person {
return this.people[id];
},
addPerson(person: v4Person): void {
this.people[person.id] = person;
}
};
// our adapter function
function v3PersonTov4Person(person: v3Person): v4Person {
return Object.assign({}, person, { birth: new Date(new Date() - 10 * 60 * 60 * 24 * 365) });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment