Logo

dev-resources.site

for different kinds of informations.

Spending Less Time on Boilerplate with Blackbird

Published at
8/12/2024
Categories
ai
aicode
codegeneration
api
Author
getambassador2024
Categories
4 categories in total
ai
open
aicode
open
codegeneration
open
api
open
Author
17 person written this
getambassador2024
open
Spending Less Time on Boilerplate with Blackbird

How Blackbird let me focus on what was important

In today’s market, time is an extremely valuable commodity. I’d like to share my thoughts on the current ways to help make the most efficient use of developers’ time and effort. I’m a developer at heart and even though I am helping to build it, here’s what gets me excited about using Blackbird API Development Platform to develop Blackbird.

How I Build Projects

Starting off, a little about my process. I tend to start a project by building out OpenAPI specs that will define the contract between the frontend, backend, and any other external clients. It’s important to have a solid understanding of these contracts to ensure my team and I can move fast in parallel and OpenAPI provides a well adopted standard for doing so. Once the spec is done, work can proceed in parallel. Now me, I’ve always been a backend person, so I’m going to focus on that part.

The Options

Today, AI is all the rage, so I started looking at GenAI and the various tools out there that can help out. But what I found, is that the current state of GenAI makes it really good at giving snippets of solutions, but going for a whole project is fraught with challenges. As things stand now, the answers are just not deterministic enough for me to accept without a high degree of changes needing to be made. So that one was off my list.

Next up was looking at more traditional OpenAPI specs. There are OpenAPI code generation libraries out there that are pretty good for client code, so I decided to check out the server side capabilities. And was soon disappointed. While client side codegen is well supported, there were many aspects of the server side offerings that I was unhappy with from lack of support to lack of flexibility.

In comes Blackbird. Blackbird API Development is a new tool from Ambassador that aims to improve the development process and efficiency. One of the features of Blackbird is code generation from an OpenAPI spec. The difference here is that in addition to basic conversion of the OpenAPI spec into code, Blackbird allows you to use templates to customize the output from the code generation. There is a default template that sets up a good project, but you can write your own templates. This is the sort of flexibility that excites me in a tool.

Actually Using Blackbird API Development Platform

Let’s start out with looking at how it was used with building out our user management service. To help me get started, I followed the quickstart to get an account setup. From there, we’ll start off looking at the OpenAPI spec that I’ll be using.

{
"openapi": "3.0.1",
"info": {
"title": "User Service",
"version": "1.0",
"description": "API for managing users"
},
"tags": [
{
"name": "User",
"description": "Operations related to users"
}
],
"paths": {
"/register": {
"post": {
"summary": "Register a new user",
"operationId": "registerUser",
"tags": [
"User"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserRegistration"
}
}
}
},
"responses": {
"201": {
"description": "User created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Bad request"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/users/{userId}": {
"get": {
"summary": "Get user by ID",
"operationId": "getUserById",
"tags": [
"User"
],
"parameters": [
{
"name": "userId",
"in": "path",
"description": "ID of the user to retrieve",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "User found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "User not found"
},
"500": {
"description": "Internal server error"
}
}
},
"put": {
"summary": "Update user by ID",
"operationId": "updateUserById",
"tags": [
"User"
],
"parameters": [
{
"name": "userId",
"in": "path",
"description": "ID of the user to update",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"responses": {
"200": {
"description": "User updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Bad request"
},
"404": {
"description": "User not found"
},
"500": {
"description": "Internal server error"
}
}
},
"delete": {
"summary": "Delete user by ID",
"operationId": "deleteUserById",
"tags": [
"User"
],
"parameters": [
{
"name": "userId",
"in": "path",
"description": "ID of the user to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "User deleted successfully"
},
"404": {
"description": "User not found"
},
"500": {
"description": "Internal server error"
}
}
}
}
},
"components": {
"schemas": {
"UserRegistration": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
},
"displayName": {
"type": "string"
}
},
"required": [
"email",
"displayName"
],
"additionalProperties": false
},
"User": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"displayName": {
"type": "string"
}
},
"required": [
"id",
"email",
"displayName"
],
"additionalProperties": false
}
}
}
}

The great thing about Blackbird is that I was able to use its AI backed design feature to help generate the OpenAPI spec. But that’s a story for another day.

Getting Blackbird API Platform

I’m using a Linux device to develop, so I downloaded the amd64 Linux Blackbird CLI binary, but there are binaries for both flavors of Mac as well as for Windows.

sudo curl -fL https://storage.googleapis.com/releases.datawire.io/blackbird/v0.3.0-beta/linux/amd64/blackbird -o /usr/local/bin/blackbird
sudo chmod a+x /usr/local/bin/blackbird

Next I logged in and was ready to go.

blackbird login

Generating My Project
At this point, I was ready to really dig in and get started. I’m now going to use the generate functionality of Blackbird to spin up my project and get the boilerplate for the service created.

blackbird code generate -t go -s user-service.json -o user-service

As this command runs, it prompts for a few details.

First up is project name. I’m keeping it simple and calling it user-service.

Enter value for variable ProjectName: user-service

Next up is the GoModule. This is the name of the module for the project. Here, I’m just following Go conventions and giving it the name of the full repo path.

Enter value for variable GoModule: github.com/kai-tillman/user-service
Now I need to enter the Go version to be set as the minimum in the module. It’s important to make sure it is at least the version of Go installed locally or older for validation to pass.

Enter value for variable GoVersion: 1.22.5
Finally, there will be a set of yes/no prompts for additional details to include such as a stubbed README or a Dockerfile that can be used with the other capabilities that Blackbird offers like Run/Debug or Deployment.

enable GitHub Actions - Test module? [y/n]: n
enable Readme module? [y/n]: y
enable Dockerfile module? [y/n]: y

At this point, Blackbird generates my project for me, sets up the APIs using Gorilla Mux, and stubs out all the handlers needed. Now I can focus on the important part of adding the business logic to the service without handling all the plumbing necessary to setup the project and handle API path routing.

Moving Forward

At this point, I’ve spent very little time and already have a project that is ready to go for implementation of the business logic. Here’s a quick view of what I have to start from after using Blackbird.

*Project structure after using Blackbird’s code generation
*

Image description

And now I just need to start building out the important parts of the service off the stub created for me. Here’s a quick pick of the file I need to start from.

`package api

import (
"context"
"github.com/rs/zerolog"
"net/http"
"os"
"time"
)`


// APIHandler is a type to give the api functions below access to a common logger
// any any other shared objects
type APIHandler struct {
// Zerolog was chosen as the default logger, but you can replace it with any logger of your choice
logger zerolog.Logger

` // Note: if you need to pass in a client for your database, this would be a good place to include it
}

func NewAPIHandler() *APIHandler {
output := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}
logger := zerolog.New(output).With().Timestamp().Logger()
return &APIHandler{logger: logger}
}

func (h *APIHandler) WithLogger(logger zerolog.Logger) *APIHandler {
h.logger = logger
return h
}

// Delete user by ID
func (h *APIHandler) DeleteUserById(ctx context.Context, userId string) (Response, error) {
// TODO: implement the DeleteUserById function to return the following responses

// return NewResponse(204, {}, "", responseHeaders), nil

// return NewResponse(404, {}, "", responseHeaders), nil

// return NewResponse(500, {}, "", responseHeaders), nil

return NewResponse(http.StatusNotImplemented, ErrorMsg{"deleteUserById operation has not been implemented yet"}, "application/json", nil), nil
}

// Get user by ID
func (h *APIHandler) GetUserById(ctx context.Context, userId string) (Response, error) {
// TODO: implement the GetUserById function to return the following responses

// return NewResponse(200, User{}, "application/json", responseHeaders), nil

// return NewResponse(404, {}, "", responseHeaders), nil

// return NewResponse(500, {}, "", responseHeaders), nil

return NewResponse(http.StatusNotImplemented, ErrorMsg{"getUserById operation has not been implemented yet"}, "application/json", nil), nil
}

// Register a new user
func (h *APIHandler) RegisterUser(ctx context.Context, reqBody UserRegistration) (Response, error) {
// TODO: implement the RegisterUser function to return the following responses

// return NewResponse(201, User{}, "application/json", responseHeaders), nil

// return NewResponse(400, {}, "", responseHeaders), nil

// return NewResponse(500, {}, "", responseHeaders), nil

return NewResponse(http.StatusNotImplemented, ErrorMsg{"registerUser operation has not been implemented yet"}, "application/json", nil), nil
}

// Update user by ID
func (h *APIHandler) UpdateUserById(ctx context.Context, userId string, reqBody User) (Response, error) {
// TODO: implement the UpdateUserById function to return the following responses

// return NewResponse(200, User{}, "application/json", responseHeaders), nil

// return NewResponse(400, {}, "", responseHeaders), nil

// return NewResponse(404, {}, "", responseHeaders), nil

// return NewResponse(500, {}, "", responseHeaders), nil

return NewResponse(http.StatusNotImplemented, ErrorMsg{"updateUserById operation has not been implemented yet"}, "application/json", nil), nil
}
`

Conclusion

And there you have it. I’m ready to go with implementing the key features of the service without worrying about the overhead of getting the project setup with an API handling framework. I definitely saved time here, and this was just for a single service. This doesn’t even factor in the time and effort saved for the other services that need to built to complete the new application I’m building.

The code generation isn’t the only useful feature of Blackbird either. As mentioned, it helped to quickly get the OpenAPI spec generated without having to spend any time in an editor. It can also be used to spin up a mock server that the rest of the team can build against while I’m working on the user service. When I’m done with implementation of the service, I can deploy the implementation out and replace the running mocks. There’s much here that can help us in different ways to spend our valuable time more effectively.

codegeneration Article's
30 articles in total
Favicon
Anvil: An attempt of saving time
Favicon
Spending Less Time on Boilerplate with Blackbird
Favicon
Boost Your Coding: Easy AI Code Generation Tricks
Favicon
How to Use AI Code Generation to Enhance Developer Productivity
Favicon
Understanding Abstract Syntax Trees
Favicon
Component Generation with Figma API: Bridging the Gap Between Development and Design
Favicon
How to Perform Code Generation with LLM Models
Favicon
Get rid of Copy/Paste with Plop Js!
Favicon
ABP Suite: Best CRUD Page Generation Tool for .NET
Favicon
Generating TypeScript Code for a Dynamic Country Flag React Component
Favicon
Top Free Code Generation tools, APIs, and Open Source models
Favicon
Introduction to Code Generation in Rust
Favicon
Best Code Generation APIs in 2023
Favicon
Crafting Prompt Templates for Code Generation
Favicon
NEW: Code Generation APIs available on Eden AI
Favicon
Declarative code generation in Unity
Favicon
Build a WebAssembly Language for Fun and Profit: Code Generation
Favicon
Using EDMX file and T4 in .NET Core to Generate Code (Entities, DTO, API, Services etc.)
Favicon
Freezed Kullanarak Flutter'da JSON Nasıl Ayrıştırılır? 💫 🌌 ✨
Favicon
Dotnet code generation overview by example
Favicon
Sparky's Tool Tips: NimbleText
Favicon
Coding the code versus coding the code that codes
Favicon
Build an entire React application in one command
Favicon
Ensure auto-generated code is always up-to-date with compile-time assertions in Go
Favicon
Sitecore Code Generation with Unicorn in 2020
Favicon
gosli: a little attempt to bring a bit of LINQ to Golang
Favicon
How to make a code generator in 5 minutes (or less)
Favicon
Adding Contexts via Go AST (Code Instrumentation)
Favicon
How to Add Generated HttpClient to ASP.NET Core Dependency Injection (Right Way)
Favicon
Using code generation to survive without generics in Go

Featured ones: