[HN Gopher] The Stainless SDK Generator
___________________________________________________________________
The Stainless SDK Generator
Author : brandur
Score : 158 points
Date : 2024-04-24 16:34 UTC (6 hours ago)
(HTM) web link (www.stainlessapi.com)
(TXT) w3m dump (www.stainlessapi.com)
| yreznikova wrote:
| Great solution!
| Harriet_ wrote:
| Been waiting for this! Nice!!
| mmcclure wrote:
| We're in the process of (slowly) switching our SDKs over to
| Stainless. I'll be transparent that it hasn't been the
| easiest/quickest process ever, but the biggest pain point so far
| is that we were forced to clean up some rough spots around our
| OpenAPI spec. It was something we needed to do anyway, so
| definitely not a knock on Stainless, but it did take the first
| SDK rollout from being a month or two to many months.
|
| The biggest reason we started working with Stainless was to
| migrate from the open source generator process we were struggling
| with, but actually ended up transitioning our hand-rolled Node/JS
| SDK first because it's both our most popular SDK and because the
| fragmentation of the JS ecosystem has been a surprisingly big
| headache[1]. Went into it for reduced maintenance, came out
| pleasantly surprised at how useful I was finding my own SDK's
| features around `for...of` on paginated routes, etc.
|
| Congrats on the launch, Stainless crew!
|
| [1] https://www.mux.com/blog/keeping-up-with-the-node-ish-
| ecosys...
| morgante wrote:
| Stainless and Alex have been great to work with! We've partnered
| with them to incorporate automated migrations for several APIs
| (ex. https://docs.grit.io/patterns/library/cloudflare_go_v2) and
| everyone at Stainless is consistently technically brilliant and
| kind.
| kmlx wrote:
| what's the difference between this and
| https://github.com/deepmap/oapi-codegen
| rattray wrote:
| oapi-codegen looks pretty cool (it seems I starred it some time
| ago) but for starters, it's Go only. Stainless does a bunch of
| languages with more on the way.
|
| You can see an example of the Go code Stainless produces here:
| https://github.com/cloudflare/cloudflare-go
|
| (I'm the Stainless founder)
| itslennysfault wrote:
| That one only does Go, but it is just one of many OpenAPI
| generators. I used openapi-generator and was able to produce
| SDKs for TypeScript (angular and node), Dart (Flutter),
| Kotlin (andoird), and Swift (iOS). They worked great for our
| purposes and I really had no complaints or issues.
|
| I guess my question is, what is the key differentiator that
| Stainless offers above just using OpenAPI and its huge
| library of existing generators?
|
| https://github.com/OpenAPITools/openapi-generator
| rattray wrote:
| If it works great for you, that's great - stick with it!
|
| From what I've seen, a lot of API developers find that
| openapi-generator flat-out breaks or produces broken code
| for their OpenAPI spec (varies language-to-language of
| course).
|
| Beyond hitting the "it just works" mark more often, some
| other key differentiators:
|
| 1. much easier to configure & customize
|
| 2. auto-retry with exponential backoff (this is huge for
| our customers)
|
| 3. auto-pagination
|
| 4. overall ergonomics of using the SDK
|
| 5. internals that look handwritten (some people care,
| others don't)
|
| 6. one-click releases to github & package managers w/
| semver, changelogs, etc
|
| 7. careful handling of edge cases (eg, will adding a new
| enum variant cause your Java client to crash?)
|
| 8. a long tail of more advanced features, like webhook
| signature verification, streaming, etc
|
| Thanks for the question - hope this helps :)
| itslennysfault wrote:
| Very helpful. Thanks for the details.
| mmcclure wrote:
| There are open source generators for a ton of languages[1]!
| We've been reasonably happy with their output for ~5 SDKs over
| the last few years. We did a massive writeup on our experience
| at the time[2].
|
| In the end...ongoing maintenance there was still enough of a
| pain that we made the decision to outsource it. For just one or
| two SDKs I think we probably would have kept going with it,
| though.
|
| [1] https://github.com/OpenAPITools/openapi-generator
|
| [2] https://www.mux.com/blog/an-adventure-in-openapi-v3-api-
| code...
| jamietanna wrote:
| Thanks for thinking of oapi-codegen (I'm one of the
| maintainers) but agreed, we only target Go - maybe that'll
| change in the future, but there are a lot of great companies
| out there working on this as a problem that I'm happy not
| trying to compete against
| Max_Horstmann wrote:
| Congrats!
|
| Any C# support in the works? :)
| segphault wrote:
| Yep! C# is on our roadmap, coming up next after we ship support
| for Ruby. I recently joined the engineering team at Stainless
| and this was one of the first questions that I asked. I'm a C#
| enthusiast and worked at Xamarin back in the day, so I'm pretty
| excited for this feature.
| gp wrote:
| This is a very exciting product - I'm surprised this didn't exist
| until now. Would love to see C++ support in the future.
| geekodour wrote:
| is this an alternative to buf.build?
| rattray wrote:
| buf.build : protobuf :: stainless : REST
|
| (roughly)
| frankfrank13 wrote:
| buf would require to switch to protobufs, which for most
| companies is huge switch
| pksunkara wrote:
| How do you guys differ against https://www.speakeasyapi.dev and
| https://www.buildwithfern.com and https://liblab.com?
| lapusta wrote:
| We've recently evaluated all four platforms--Stainless, Fern,
| Speakeasy, and Liblab--and here are our key takeaways:
|
| Stainless: The standout for maturity and idiomatic code
| generation. While method signatures across products may look
| the same, Stainless shines during developing & debugging -
| making their codebase easier to navigate. They have a practical
| separation of SDK configuration from OpenAPI specification,
| setting it apart from others reliant on OpenAPI overlays. The
| Stainless Studio also proved invaluable for refining our
| OpenAPI specs during our exploration phase.
|
| Fern: Notable for being open-source, though not free. It
| provides a robust end-to-end Developer Experience, covering
| everything from SDKs and documentation to Postman collections.
| Fern uses an internal "Fern Definition" language (~ think
| Smithy), it's optional and enables capabilities like merging
| multiple specs, but is adding another layer to navigate in our
| view.
|
| Speakeasy: Moves at a fast pace, which could be a double-edged
| sword. Rapid iterations may lead to frequent, potentially
| disruptive updates for customers. A minor gripe was the
| inclusion of "Speakeasy" in class names, which felt overly
| branded.
|
| Liblab: Initially limited in language support, they've expanded
| but still lag behind in establishing a strong customer base,
| which might be a red flag for some adopters.
|
| BTW all folks are very approachable and collaborative!
| alalani1 wrote:
| When you say it shines during development and debugging - can
| you provide examples of what types of problems it solved?
| TehShrike wrote:
| I'm curious to see the output. Most of the time I specifically
| avoid using the SDK published by the API provider because it's
| thousands of lines of code when all I need is ten lines wrapping
| fetch.
| rattray wrote:
| We try to keep it to a minimum, especially in JS (though we
| have some nice improvements coming soon when we deprecate node-
| fetch in favor of built-in fetch). The package sizes aren't
| tiny because we include thorough types and sourcemaps, but the
| bundle sizes are fairly tidy.
|
| Here's an example of a typical RESTful endpoint (Lithic's
| `client.cards.create()`:
|
| https://github.com/lithic-com/lithic-node/blob/36d4a6a70597e...
|
| Here are some example repos produced by Stainless:
|
| 1. https://github.com/openai/openai-node 2.
| https://github.com/openai/openai-python 3.
| https://github.com/cloudflare/cloudflare-go 4.
| https://github.com/Modern-Treasury/modern-treasury-java
| jacoblee9315 wrote:
| Congrats on the launch! Incredible customer list!
| sloanesturz wrote:
| We've been happy customers of Stainless since the beginning. Alex
| and the team are thoughtful and care a lot about API design.
| Recommended!
|
| https://github.com/increase
| GeneticGenesis wrote:
| Disclaimer: We're an early adopter of Stainless at Mux.
|
| I've spent more of my time than I'd like to admit managing both
| OpenAPi spec files [1] and fighting with openapi-generator [2]
| than any sane person should have to. While it's great having the
| freedom to change the templates an thus generated SDKs you get
| with using that sort of approach, it's also super time consuming,
| and when you have a lot of SDKs (we have 6 generated SDKs), in my
| experience it needs someone devoted to managing the process,
| staying up with template changes etc.
|
| Excited to see more SDK languages come to Stainless!
|
| [1] https://www.mux.com/blog/an-adventure-in-openapi-v3-api-
| code...
|
| [2] https://github.com/OpenAPITools/openapi-generator
| pm wrote:
| A couple of months ago I tried to generate a Swift SDK using
| Cloudflare's OpenAPI spec with Apple's Swift OpenAPI generator,
| to no avail. Apple's generator is quite strict from what I can
| tell, so I'm curious about the differences between Stainless' and
| Apple's work.
|
| (I haven't tried it since then, so it may work now).
| ramijames wrote:
| That is a very nice landing page.
| addcn wrote:
| Alex and team are top notch. Met some of their customers who also
| use Optic and hear great things.
|
| Definitely the place to go these days if you have a public API
| and want it to be as developer friendly as they come.
| aazo11 wrote:
| We use Stainless. Great team and product.
| androa wrote:
| I've been using openapi-generator for this for quite some time. I
| love the principle, but find that their generators are not all
| top notch, and the template-based approach is very hard to
| contribute to.
|
| One thing I do not see with Stainless is also using it to
| generate the server side of the API.
|
| We use OpenAPI as a design doc, and generate client SDKs and
| Datos for the server from the same spec. This gives us pretty
| solid control over interoperability.
| itslennysfault wrote:
| Thats what I used at my previous startup and had a lot of luck
| with it. Our backend used NestJS which allowed us to define
| APIs and DTOs using decorators which automatically generated
| OpenAPI json files. Then our clients would load the json and
| generate their own SDK as a library. It worked extremely well.
| It worked so well that I don't really understand what problem
| Stainless is solving. We didn't ship public SDKs. So, maybe
| that's what I'm missing, but for our internal clients OpenAPI
| generators worked great. We generated SDKs on the fly for
| TypeScript (Node services and Angular clients), Dart (flutter),
| Kotlin (android), and Swift (iOS). For the most part it all
| "just worked" for our use case.
| rattray wrote:
| > We didn't ship public SDKs. So, maybe that's what I'm
| missing
|
| Yeah, Stainless is more focused on public SDKs, which can be
| a lot trickier and more demanding.
|
| Some more details replying to your other comment here:
| https://news.ycombinator.com/item?id=40148629
| Bjartr wrote:
| > their generators are not all top notch, and the template-
| based approach is very hard to contribute to.
|
| I agree, I love them in theory, but wrangling mustache
| templates is a hassle. They end up super coupled to the code
| that populates them which makes them a bear to modify.
| brandur wrote:
| I helped maintain the public API language bindings at Stripe for
| many years, and although I'd defend it to the death as the right
| DX (SDKs are _so_ much easier to use than raw web APIs), it 's
| hard to appreciate just how onerous of a process this was before
| Alex pioneered a codegen-based solution.
|
| Some of the language SDKs were dynamic (e.g. Ruby, Python) and
| could adapt automatically to small API changes like if a new
| field was added in a response. Some were not (e.g. Java, Go), and
| for every API change, someone had to manually make the change to
| that SDK's codebase (add a field, add a struct, add a function
| for a new endpoint), get it reviewed and merged, and cut a
| release. As Stripe got bigger and there were API changes all the
| time, the only way this was even remotely functional was that we
| had a couple heroic workhorses that'd watch for changes and open
| hundreds of PRs a year for them. Honestly, in retrospect, it's
| amazing this even worked.
|
| Getting everything switched over to a generated solution was an
| arduous process because the new generated code had to be API-
| compatible with the existing code so that the cutover to
| generated bindings didn't break every Stripe user under the sun.
| We eventually got there, but it took a long time.
|
| I like Stainless' mission because after seeing the crazy
| maintenance hassle that all of this was at Stripe, I think it
| makes way more sense to save all that engineering time for your
| more concerns, and outsource this problem to someone else. A
| plug-and-play way of getting high quality SDKs in all common
| languages that get pushed to appropriate repositories for
| distribution and comes with quality companion documentation.
|
| We've actually had pretty good luck at my current job with open
| source tools like openapi-generator [1], but this sort of codegen
| is so convoluted that the sustainability of a pure open source
| solution makes me a little afraid, and you still end up doing a
| lot of the last mile work yourself.
|
| ---
|
| [1] https://github.com/OpenAPITools/openapi-generator
| mythz wrote:
| Never been a fan of code-gen solutions that generates both DTOs
| and client proxies which IMO promotes a lot of fragile code-gen
| that's less resilient to changes. The solution we've adopted in
| ServiceStack to solve this with minimal code generation and
| better reusability is to only generate the message-based Typed
| DTOs for each language but have them able to be used by the
| same generic JSON ServiceClient.
|
| This approach takes minimal effort since we only need to
| generate Typed DTOs in each language, which all works the same
| way, where you use the same generic `JsonServiceClient`
| (created once per language/platform) that use same methods to
| make API requests making it easy to for our built-in API
| Explorer [1] (Live Demo [2]) to auto generate API pages for all
| 11 supported languages which also supports dynamic languages
| like JS/TS, Python and PHP with additional type hints [3].
|
| [1] https://docs.servicestack.net/api-explorer#code-tab
|
| [2] https://vue-vite-
| api.jamstacks.net/ui/QueryBookings?tab=code
|
| [3] https://docs.servicestack.net/add-servicestack-reference
| brandur wrote:
| That's interesting -- the typed request and response objects
| that you get with an API SDK are certainly one of the biggest
| upsides, and I certainly see the benefit of having full
| control of the transport layer yourself so that you can add
| any common telemetry / logging / statistics at your leisure.
|
| On the other hand, a benefit of a more complete API is that
| in typed languages you can tab-complete your way to success.
| With each endpoint a function and all the requisite
| configuration (API URL, etc.) bundled in, for basic
| integrations you may never even have to reference
| documentation, or if you do, very little of it, as your IDE
| finds functions/properties for you and you can read
| documentation right out their docstrings.
| mythz wrote:
| Sending Messages also have many benefits over RPC methods
| [1] that's especially important for evolvable APIs across
| process boundaries.
|
| The DTOs are still typed so you still get AutoComplete that
| also include API Docs and Type hints in the generated DTOs
| since we full control how DTOs are generated and we're able
| to capture richer type information in the server C# DTOs
| (used as blueprints to generate DTOs in different
| languages).
|
| All the information about how to call the API and what it
| returns is captured in the Request DTOs, and the only thing
| the Service Clients need is the BaseUrl for where the APIs
| are hosted. So you could create a higher level SDK client
| that just needs to inherit the Service Client and hard code
| its URL, e.g:
|
| class MyClient : JsonServiceClient(BaseUrl) {}
|
| Where they'll also be able to add any helper methods
| specific to their APIs (e.g. Custom Auth). For the trade-
| off of not being able to reuse that client to call
| different APIs and endpoints, but will still share the same
| base class so you could still create reusable functionality
| that can be shared across all Service Clients.
|
| [1] https://docs.servicestack.net/advantages-of-message-
| based-we...
| jenanwise wrote:
| I've been working with the Stainless team over the last year, and
| I rapidly went from being highly skeptical of outsourcing SDKs to
| being one of the team's biggest fans.
|
| Separately, as a consumer of many afterthought-ish SDKs for
| popular services, I'm extremely excited to see their work opening
| up to more developers. I hope it raises the bar for SDKs
| everywhere!
| ezekg wrote:
| I run an API SaaS and this looks great. I've wanted to add more
| SDKs to my lineup, but it's a big commitment for a single person
| to maintain. Right now, I have a minimal Go SDK to test the
| waters and definitively see how much effort it takes. One hurdle
| I have with services like Stainless (which looks great!) is that
| I don't know where to start to actually codegen an OpenAPI spec
| from a Ruby/Rails API. Writing and maintaining the spec by hand
| sounds like a nightmare for any decent sized API, but I guess
| it's not worse than maintaining multiple SDKs (or even one). How
| did Stripe handle this?
| amne wrote:
| From experience I can tell you that you just need to do it
| manually. And keep maintaing the spec manually. That keeps you
| aware of it. It is your contract with the outside world. You
| don't want tools to automatically update it everytime you push.
| On the contrary, you want tools to slap you hard every time you
| push and you forgot to update the spec. Now you're going to
| think hard why did you change the spec and if you maybe need to
| bump to /api/v2/*
|
| As far as generating the first spec from an existing API I
| think there was shown here on HN a while ago a tool that can
| generate the spec from you just "browsing" the API with
| developer tools or something. Maybe even from a HAR file IIRC.
| rattray wrote:
| > I don't know where to start to actually codegen an OpenAPI
| spec from a Ruby/Rails API... How did Stripe handle this?
|
| Stripe built their own Ruby DSL for exactly this, and it worked
| great. It looked something like this: class
| CreateCustomerMethod < AbstractAPIMethod endpoint
| "post /v1/customers" returns CustomerAPIResource
| required :name, String, description: "The name of the
| customer." optional :description, String,
| description: "Additional information about the customer."
| def execute # ... end end
|
| Unfortunately, I don't know of an open-source equivalent for
| Ruby; FastAPI in Python is the closest I know of in any
| language. At Stainless, we're building a TypeScript version of
| this, but it's still deep in dogfooding.
|
| Most people just end up maintaining by hand and using a tool
| like Optic or Akita to check that it's not wildly out-of-date.
| shidoshi wrote:
| Congrats, folks.
| htunnicliff wrote:
| Question for Stainless folks: For Node.js/JavaScript SDKs, did
| you consider using Proxy instances in lieu of classes for each
| resource, to reduce the amount of actual code generated?
| rattray wrote:
| Yeah, we opted for classes like this to ensure simple, reliable
| static typing.
|
| The amount of runtime code generated per endpoint is typically
| around 3 short lines - here's an example, also posted elsewhere
| on this thread: https://github.com/lithic-com/lithic-
| node/blob/36d4a6a70597e...
| ucarion wrote:
| Lots of these have been popping up lately, they all seem really
| good. This is the first one I've seen with an answer for "I want
| to customize the output by hand", which is neat.
|
| https://buildwithfern.com/ https://liblab.com/
| https://www.speakeasyapi.dev/
|
| Between this and fancy API doc generators (Mintlify, etc.), I
| feel like we're finally getting close to being able to just go
| from OpenAPI to Stripe-level "polish".
| dgellow wrote:
| We have to improve our docs a lot, but yes, that's the goal!
| You as a stainless user should feel empowered and in control :)
| bterlson wrote:
| Grats on the release! It will be awesome to see how far y'all can
| push codegen quality from an OpenAPI source of truth.
|
| I worked on this extensively inside Azure and I know it is not an
| easy problem (and with more JSON Schema coming in 3.1/4.0, it is
| only getting harder). There are a lot of API patterns that you
| want to expose purpose built client abstractions for. Pagination
| is a big example. If you stick to OpenAPI, you have to ensure
| your specs use the patterns your client generator recognizes, and
| it's not always trivial for authors to know how to express that
| pattern and for your codegen to infer that pattern. In Azure we
| tended to rely more on custom extensions to OpenAPI because it
| made the contract a lot clearer and less error prone, but then
| you lose interoperability.
|
| One thing to consider - I work on TypeSpec[1], and one of the
| main reasons we built it is to allow encapsulation and reuse of
| patterns in a first-class way. So, rather than the contract being
| "endpoints which declare parameters and/or return types with
| these shapes are inferred to be paginated endpoints", the
| contract can be "use Page<T>" and the emitted OpenAPI conforms to
| the contract without effort. It would be fun to see a Stainless
| TypeSpec library for all the patterns your codegen supports!
|
| 1: https://typespec.io
| ashryan wrote:
| I've been keeping a simple list of SDK generators here:
| https://gist.github.com/ashryanbeats/7806079820e0c83b35b94ae...
|
| Nothing fancy but maybe more helpful than picking through SEO
| articles in you're looking for options.
| bgentry wrote:
| I was fortunate to be able to watch Stainless evolve while at Mux
| when we were evaluating it to replace open source & homegrown
| generators. Alex & team were incredibly responsive to feedback
| throughout, and the product improved quickly.
|
| Generated code has a bad rap for results that are not idiomatic
| or user-friendly, but I think it's clear that it doesn't have to
| be that way--getting it right just takes a lot of care and effort
| that is typically overlooked.
|
| Stainless is definitely best-in-class here as far as I've seen.
| nknj wrote:
| Proud to have Stainless as a partner at OpenAI. All our SDKs are
| generated by them. Alex, Robert, Philip and team are extremely
| thoughtful about SDK design and push us to improve our API +
| other products while they're at it. Stainless is bringing a new
| standard of SDKs to our industry and this is a great thing for
| all developers.
| vvoyer wrote:
| What about jsii? The technology behind AWS cdk sdks:
| https://aws.github.io/jsii/
|
| Is Stainless similar, different?
| enepture wrote:
| Congratulations to Alex and the team. After chatting with them
| recently at a NYC meet-up, you can tell they live and breathe
| client SDK generation. They are thoughtful about creating an
| excellent experience for those generating SDKs (Stainless Studio)
| and those consuming them. Good luck!
| serjester wrote:
| Out of curiosity how difficult has it been hosting OpenAI's SDK?
| Is there one off stuff that goes into that? Their API seems
| really challenging - SSE's, tons of complex generics, async, docs
| - it's endless.
|
| Really impressive stuff, I can't imagine doing all this with a
| team as lean as yours.
| beastman82 wrote:
| Is gRPC too easy or ?
___________________________________________________________________
(page generated 2024-04-24 23:01 UTC)