December 4, 2024
Dennis Ameling, Engineering Manager
Leap’s software-only solution allows our partners to launch and scale their own virtual power plants (VPPs), turning their energy resources into revenue. To make this possible, our team has developed an extensive API suite that makes it easy for partners to enroll their devices like battery storage, EVs, and HVAC systems into grid programs through Leap’s platform. But it doesn’t stop there: bidding, dispatching, and performance insights can all be automated using our APIs. This drives scalable operations, for both Leap and our partners.
Since our teams are organized around the various aspects of the VPP operational journey mentioned above, we were looking for a way to offer a consistent experience around the various API endpoints that Leap offers to our partners. Think about naming of paths, property casing (snake_case, camelCase), etc.
All of Leap’s API endpoints are defined through industry-standard OpenAPI specifications. While that’s a great starting point, we encountered challenges in deciding between two approaches: code-first or schema-first?
❓Code-first v.s. schema first OpenAPI specs: What’s the difference?
In a nutshell, here’s the difference:
Schema-first: Plan the API first, generate server code from that plan.
Code-first: Write the server code first, generate the API spec afterward (with annotations).
This seemingly small distinction has significant practical implications.
The practical implications: Lessons learned from code-first
At Leap, we were using the code-first approach in many of our projects. This boosted our development speed, because we could quickly build something that works, then annotate our endpoints and data models with OpenAPI annotations. We used the javalin-openapi library to then generate the OpenAPI specs for us.
However, we encountered several challenges with this approach:
Frequent iterations post-deployment. Important internal and partner feedback would come in after the fact, because it can be difficult for users to fully grasp an API’s functionality until they can actually use it. This sometimes required significant rewrites of the backend code as a result.
Lack of consistency. Some terms within Leap are used across the board, like Transmission Region (familiar from Leap’s Meters API). However, different teams ended up duplicating lists or terms across various APIs, creating inconsistency.
Mismatch between the spec and actual API behavior. Take this example below, where we struggled with formatting issues when setting arrays in our OpenAPI spec. Initially unsupported by the library we used, the array format appeared incorrectly until a later update allowed us to fix it. We only caught these issues after generating and manually reviewing the OpenAPI spec. Here’s a before and after of this example:
🏆 Leap's preferred solution: schema-first OpenAPI
One of our teams was already using schema-first OpenAPI specifications with the kotlin-spring code generator. This method takes your OpenAPI spec as YAML or JSON, and generates interfaces for your server - in our case, data classes and API endpoints that we could simply inherit from.
The biggest change here is that designing the API becomes one of the first steps in the process. That’s huge, because it allows you to:
Ensure consistency with other APIs: property naming, casing, reuse of schemas, etc.
Gather early feedback from internal stakeholders and partners, before building the actual API. This reduces the need for significant changes later on because more time and thinking went into the API design upfront. New API specs are also proposed to a team of internal stakeholders, which helps us catch potential problems early.
Make the code behave exactly according to spec. When you set a property to be of type
string
in your spec, but forgot to setformat: uuid,
you’ll notice it immediately in the generated code, because it will become aString
instead of aUUID
property in Kotlin.
Contributing to open source OpenAPI generators
While we found the kotlin-spring OpenAPI generator useful, we often prefer lighter frameworks like Javalin for the APIs Leap offers.
The challenge was that the OpenAPI generator didn’t have a generator yet for Javalin, so we took the initiative to contribute one! We’re now integrating this new generator in more projects, and we’ve been incredibly productive with this setup.
Conclusion: API quality and internal efficiency improved
We’ve been seeing these benefits from the schema-first OpenAPI approach so far:
More consistent API endpoints in terms of naming, reuse of properties (e.g.,
transmission_region
). We're continuing to refine our existing API endpoints to maintain this standard.Earlier, more effective feedback. By moving API spec review to the beginning of the process, we build the right features for the right audience more efficiently.
Generated code helps kickstart projects quickly. We just have to implement the interfaces, but all the data classes are generated for us. This is a game changer!
And there you have it! This is how Leap is taking its API development to the next level. Let us know if you have any questions or feedback on this article, and stay tuned for more posts from our engineering team.