Skip to content

Instantly share code, notes, and snippets.

@gilligan
Created August 26, 2017 16:52
Show Gist options
  • Save gilligan/dce2df5a4dbc168be21fe2fabbf7235c to your computer and use it in GitHub Desktop.
Save gilligan/dce2df5a4dbc168be21fe2fabbf7235c to your computer and use it in GitHub Desktop.
// @flow
// So `declareService` receives a service declaration that provides some properties
// for creating a service including some functions for mapping service responses and
// query parameters. I thought it would be nice to parameterize `ServiceDeclaration`
// over the types of those mapping functions.
//
// Am I making sense? Does the *code* make any sense? If not how should I change it?
/* types.js -------------------------------------------------------- */
interface ServiceDeclaration<Response, MappedResponse, QueryParams, MappedQueryParams> {
name: string,
servicePath: string,
transform(from: Response, to: MappedResponse): MappedResponse,
toQuery(from: QueryParams, to: MappedQueryParams): MappedQueryParams,
passThroughCacheHeaders: boolean,
serviceTimeout: number,
errorHandler: Function
}
/* declareService.js ---------------------------------------------- */
const declareService = (decl: ServiceDeclaration<*, *, *, *>) => {
return {
createService(baseUrl) {
return {
name: decl.name,
read(req, res, params, config, callback) {
// fetch data using functions/props from decl
}
};
}
};
};
/* fooService.js -------------------------------------------------- */
type Response = {| bar: string |};
type FooResponse = {| foo: string |};
type FooParams = {| id: string |};
type FooMappedParams = {| id: string, token: string |};
const fooErrorHandler = (err) => { throw err };
const fooTransform = (r: Response): FooResponse => {
return {
foo: r.bar
};
};
const fooQuery = (params: FooParams): FooMappedParams => {
return {
id: params.id,
token: 'token'
};
};
const fooDecl : ServiceDeclaration<Response, FooResponse, FooParams, FooMappedParams> = {
name: 'fooService',
servicePath: '/api/foo',
transform: fooTransform,
toQuery: fooQuery,
passThroughCacheHeaders: false,
serviceTimeout: 5000,
errorHandler: fooErrorHandler
};
declareService(fooDecl);
/* barService.js -------------------------------------------------- */
type _Response = {| bar: string |};
type BarResponse = {| foo: string |};
type BarParams = {| id: string |};
type BarMappedParams = {| id: string, token: string |};
const barErrorHandler = (err) => { throw err };
const barTransform = (r: _Response): BarResponse => {
return {
foo: r.bar
};
};
const barQuery = (params: FooParams): BarMappedParams => {
return {
id: params.id,
token: 'token'
};
};
const barDecl : ServiceDeclaration<_Response, BarResponse, BarParams, BarMappedParams> = {
name: 'fooService',
servicePath: '/api/foo',
transform: barTransform,
toQuery: barQuery,
passThroughCacheHeaders: false,
serviceTimeout: 5000,
errorHandler: barErrorHandler
};
declareService(barDecl);
@gilligan
Copy link
Author

Okay so let's try to make some sense of this. So we are talking about a React/Fluxible app which uses fetchr plugin for REST services. So somewhere in the code base we'll do something like ..

services.forEach((s) => fetchr.registerService(s));

where s has to be something like

type Service = {|
  name: string,
  read: Function
|};

For simple services we have baseService.js which exports a declareService function which takes an object "declaring" the service and returns a Service object as expected by fetchr.

So if you want to implement someSimpleService.js:

import { declareService} from 'baseService';
const myServiceResponseMapper = (response) => doSomethingFancy(response);
const myErrorHandler = (err) => { whatever(err); };
export default declareService({
  name: 'someSimpleService',
  servicePath: '/api/simpleService',
  transform: myServiceResponseMapper
});

which would then be used somewhere in the app setup code ...

import someSimpleService from 'someSimpleService';
const c = someSimpleService(someBaseUrlFromConfig);
fetchrPlugin.registerService(c);

  • I am not overly fond of this design. In fact it has already proven itself somewhat unsuccessful when people started adding more and more props to the "service declaration object" and eventually wrote more complex services without this wrapper. I tried refactoring it altogether but that has proven to be too much of a PITA for now.
  • I could just add some rather generic ServiceDecl type where functions are just typed as Function and be done with it. Having response types in there as parameters would definitely seem nice to me though.

Not entirely sure why but nothing that I try with flow right now seems quite right. Did this explanation make it any less confusing? I hope so ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment