3 ms·
A few things I see rarely discussed that I often do: - Always use a response envelope - HTTP status is not the same as your application status. I always inclu
by hardwaresofton 5y ago
A few things I see rarely discussed that I often do:
- Always use a response envelope
- HTTP status is not the same as your application status. I always include a "status" field in the response envelope (OP recommends standardizing errors but I think standardize all of it)
- Always have an unique error code for every error your API can return (they should also be URL safe ("some-thing-went-wrong"), basically an enum of these should exist somewhere, the more specific the better.
- Offer OpenAPI whenever you can.
- There is no such thing as a publicly exposed "private" API. If it can be hit (and is not protected), it eventually will be.
- Do blackbox testing of your API, E2E tests are the most important kind of test you could have.
- (controversial) build in special test/debug endpoints -- these help with blackbox testing.
- metadat 5y agoAre you able to elaborate on what is a response envelope? Edit: Nevermind, see [0]. It's the simple and intuitive concept of encasing the payload in an consistently structured object which includes metadata, e.g. {"Error": null, Data: ...} [0] https://stackoverflow.com/questions/9989135/when-in-my-rest-api-should-i-use-an-envelope-if-i-use-it-in-one-place-should-i https://stackoverflow.com/questions/9989135/when-in-my-rest-... Makes intuitive sense, as it's then easier to develop a nice, generic REST client.
- hardwaresofton 5y agoHere's an example straight from code I've rewritten at least 5 times because I'm allergic to saving myself time: export enum ErrorCode { InvalidEntity = 1, NotSupported = 2, UnexpectedServerError = 3, InvalidRequest = 4, Validation = 5, // ... } export enum ResponseStatus { Success = "success", Error = "error", } export class ResponseEnvelope<T> { public readonly status: ResponseStatus = ResponseStatus.Success; public readonly data: T | Pruned<T> | null = null; public readonly error?: { code: ErrorCode, message: string, details: object | string, status: ResponseStatus, }; constructor(opts?: Partial<ResponseEnvelope<T>>) { if (opts) { if (opts.status) { this.status = opts.status; } if (opts.data) { this.data = opts.data; } if (opts.error) { this.error = opts.error; } } if (!this.data) { return; } // Prune if the thing is prunable this.data = prune(this.data); } } Hopefully this isn't too hard to grok, it's Typescript. BTW, If you're cringing at the number scheme for the ErrorCode enum, don't worry I am too. This is why I prefer to use strings rather than numbers, and it basically just ends up being stuff like "invalid-entity", etc.
- JackFr 5y agoI completely disagree. I find envelopes to be unnecessary cruft that just junk up otherwise clear code. And I think packing metadata into an envelope along with the actual data keeps people from thinking clearly about their own APIs, mostly with respect to ambiguity surrounding the word “error”. Validation errors are errors and network failures are errors, but they’re very different animals and should never be conflated. I don’t want to check for a status code in metadata ever, when an HTTP status is provided. I don’t want to read a time stamp if that time stamp is simply a property of the request. However if the time stamp is a property of the data, then I care - but then it shouldn’t be part of the metadata.
- hardwaresofton 5y agoWell I imagine this is why sages like uncle bob and sam newman will always be needed. > Validation errors are errors and network failures are errors, but they’re very different animals and should never be conflated. > I don’t want to check for a status code in metadata ever, when an HTTP status is provided. These seem like conflicting views. If the HTTP status is all you check you must be conflating application/operation status and network status. > I don’t want to read a time stamp if that time stamp is simply a property of the request. However if the time stamp is a property of the data, then I care - but then it shouldn’t be part of the metadata. Sure -- I'd like to say that I think the disagreement here boils down to this question: should metadata be represented in data responses. Correct me if I'm reading you incorrectly, but it seems like you're arguing that metadata should not be represented in the response, only data. I'd argue that the in-practice and academic standards dictate the opposite, with standards like hypermedia, json schema, HAL, JSON:API, OpenAPI, etc. If it's just a question about the degree of metadata that should be present then that's one thing, but it sounds like you're against it in general. Once you have any metadata you must have an envelope of some sort, by definition (whether it's a good or bad one), as far as I can see.
- deleted 5y ago[deleted]
- 5y ago
- scurvy_steve 5y ago- Always use a response envelope I would mostly agree except in 1 case, streaming data is easier without an envelope. Making some array inside an envelope stream is usually more code and messier than just getting some metadata out of header. So if you have something like data integration endpoints and you expect someone could pull many megs of records, consider no envelope.
- hardwaresofton 5y ago“Always” was probably a bit too absolute in my original phrasing. I agree, and would go so far as to say streaming data would probably have a different delivery mechanism all together - SSE/websockets/etc. if you were doing long polling I may still want to put the metadata in the body but I agree it could be kept out of sight quite nicely in the headers.