4 ms·
> Problem: while OpenAPI is great for describing APIs, generating client SDK code and defining your API contracts, it's definitely not a great experience to wri
by BerislavLopac 7y ago
> Problem: while OpenAPI is great for describing APIs, generating client SDK code and defining your API contracts, it's definitely not a great experience to write OpenAPI documents from scratch.
Personally, my main problem with OpenAPI is that it is commonly used for documenting contracts, but very rarely for actually driving the implementation. In practice, I have seen a few implementations like OpenAPI Generator [0] but a) with dynamic languages like Python code generation is an anti-pattern, and b) even disregarding the former, the generated result is (in my experience) very incomplete and still needs a lot of manual coding.
I would like to see more frameworks that utilise OpenAPI for routing and request/response validation.
[0] https://openapi-generator.tech/ https://openapi-generator.tech/
- jbmsf 7y agoIt's always a bit risky to document contracts with the left hand and implement them with the right. The best case to me is a framework that serves both functions at once. I built something like this at my last job by having an opinionated layer on top of Flask in Python. The idea was to have a small set of standard operation types (think:create, retrieve, update) and choices as to input/output types (via the marshmallow library ) and the function to process the route. This turns out to be enough to generate both the api contract and the boilerplate for routing, (un)marshalling, etc. You can find the implementation in this repo: https://github.com/globality-corp/microcosm-flask https://github.com/globality-corp/microcosm-flask
- BerislavLopac 7y agoYes, FastAPI [0] takes a similar approach. The problem here is that the single-source-of-truth for your contract is not OpenAPI, but rather your Flask implementation, with the OpenAPI being a documentation artefact; this is problematic for two reasons: 1. Just like any other documentation, it is difficult to verify that the documentation is actually correct and does not diverge from he underlying implementation. This is a lesser issue, as it can be mitigated with good integration testing practices. 2. The bigger issue, for me, is that any clients depends on the service implementation. In practice, it is very common to build clients -- especially complex ones like React or mobile UI -- alongside the service development, and if the spec is defined first both can be done in parallel. With the above approach you either need to a) wait for the service implementation to be complete before you can start implementing the client, or b) base the client on an OpenAPI spec which might potentially differ from the one your framework will generate based on the implementation. I have recently worked on a project which had that same issue, and our initial solution was to build tests which would compare the generated OpenAPI with the design specification, but that turned to be extremely complex when we started running into all of the edge cases. The alternative was to treat OpenAPI as the single-source-of-truth, using it to generate routes and execute validation over requests and responses. The first attempt used Connexion [1], which proved to be a bit too incomplete for our needs, so we implemented an alternative framework [2] (which includes a basic client-side support as well). [0] https://fastapi.tiangolo.com/ https://fastapi.tiangolo.com/ [1] https://connexion.readthedocs.io https://connexion.readthedocs.io [2] https://github.com/berislavlopac/pyotr https://github.com/berislavlopac/pyotr (still under heavy construction)
- jbmsf 7y agoThere's a lot to unpack here, but I'll focus on #2, specifically the client dependency. My approach is to assign projects to teams with members who can work on both the client and server layers so that the question is less of client blocking on server and more of client and server working together. In addition, I like an "API first" approach, which in this case means building the signature of the API functions (e.g with mock data) before finishing the implementation. That is: client is still blocked on server to define the API, but they are not blocked on implementing the API and client works closely enough with server (or is able to do both) such that they aren't blocked on the definition. As always, many software problems devolve into people problems once you stare at them hard enough.
- Rotareti 7y ago> ... and if the spec is defined first both can be done in parallel. You could simply write the complete interface down in Python/FastAPI without actual implementation and generate the OpenAPI spec from that interface. That way both teams could start soon.
- BerislavLopac 7y agoOh most definitely, there is a bunch of ways to solve this concrete problem. But none of them addresses the conceptual issue of the specification authority.
- jbreiding 7y agoSorry, I've heard this statement a few times, code generation in dynamic languages is an anti pattern. Do you happen to have any more reading on this? Sure the generated code is usually not readable which is against the zen of python. But writing code to generate code, especially if it's contract driven, is the correct approach no matter the language. So really what I'm looking for is more substance of why generating code is an anti pattern in python but not in C.
- ptx 7y agoMy personal feeling is that it just doesn't fit into the workflow. With C you always have a compilation step and thus a build process, so a code generation step fits quite naturally into your makefile (or other build script). With Python there's normally no build step – you just run the program. Adding code generation means adding another step, which you might forget to run, leading to confusion. If you make the build step mandatory you lose some of the upside of dynamic languages.
- jbreiding 7y agoI've always solved this with unit/functional tests. The test runner effectively acts as a compiler, for the purposes of checking the contact. in this process. This is something that's baked into a gate check on merge to the source repo. Or rather it should be.
- heavenlyblue 7y agoYou can generate Python classes on the fly during the initial import stage - because Python does it anyway itself, that’s why it’s an anti-pattern.
- jbreiding 7y agoSo code generation isn't an anti pattern, persisting the artifacts generated is? To me that is still code generation, one is more durable and able to be tested, the other is a runtime exception. I've not seen any long lived python projects that don't end up implementing runtime checks to validate the shape of these generated objects.
- tyldum 7y agoIn Perl there is a framework called Mojolicious that has an OpenAPI plugin that does not use a code generator. I find it extremely elegant as it uses annotations in the spec to route the endpoints to the desired controller method. We often need several dialects of our API, and now we can just deploy the same code, but with an altered spec. Very little overhead.