In this blog post, we will demonstrate how to integrate Riak KV with a simple Gin HTTP API using Docker Compose. We will create an API that allows users to create their favorite pizza and retrieve it.
Riak KV is a distributed NoSQL key-value data store designed to provide high availability, fault tolerance, and scalability. It is an open-source database that is used to store and retrieve data from a distributed environment. Gin is a popular Golang web framework that provides a fast and minimal HTTP server.
Prerequisites
Before proceeding, make sure you have the following software installed on your machine:
Set up the project structure
Create a new directory named pizza-api
and create a new file named docker-compose.yaml
in the directory. Create another directory named src
in the pizza-api
directory. In the src
directory, create a new directory named “pizza” and create two new files in it: main.go
and handler.go
. The project structure should look like this:
pizza-api/
├── docker-compose.yaml
└── src/
└── pizza/
├── handler.go
└── main.go
Create a Dockerfile for the Golang app
Create a new file named “Dockerfile” in the “pizza-api/src/pizza” directory with the following contents:
FROM golang:alpine
RUN apk update && apk add --no-cache git
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o main .
EXPOSE 8080
CMD ["/app/main"]
Note: This
Dockerfile
could be optimised to use multi-stage before heading to production.
This Dockerfile uses the Alpine Linux-based Golang image, installs Git to download dependencies, sets the working directory, copies the application code, downloads the dependencies, builds the application, exposes port 8080, and starts the application.
Define the Riak KV Docker container
Add the following service definition to the docker-compose.yaml
file:
version: "3.8"
services:
riak:
image: "basho/riak-kv:latest"
environment:
- "RIAK_AUTOMATIC_CLUSTERING=true"
- "RIAK_SEARCH_ON=true"
ports:
- "8087:8087"
- "8098:8098"
This will download the latest Riak KV image from Docker Hub, set the environment variables to enable automatic clustering and search, and expose ports 8087 and 8098 for client and HTTP traffic, respectively.
Implement the Gin HTTP API
Open the main.go
file and add the following code:
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/pizza", createPizza)
r.GET("/pizza/:id", getPizza)
if err := r.Run(":8080"); err != nil {
panic(err)
}
}
func createPizza(c *gin.Context) {
// TODO: Implement creating pizza in Riak KV
c.JSON(http.StatusCreated, gin.H{"message": "Pizza created"})
}
func getPizza(c *gin.Context) {
id := c.Param("id")
// TODO: Implement retrieving pizza from Riak KV
c.JSON(http.StatusOK, gin.H{
"id": id,
"name": "Margherita",
"crust": "Thin",
"size": "Medium",
})
}
This code defines two endpoints: /pizza
for creating a new pizza and /pizza/:id
for retrieving a pizza by its ID. The implementation for creating and retrieving pizzas will be added later in this tutorial.
Connect to Riak KV
Open the handler.go
file and add the following code:
package pizza
import (
"github.com/basho/riak-go-client"
)
var client *riak.Client
func init() {
var err error
client, err = riak.NewClient(&riak.ClientConfig{
Address: "riak:8087",
})
if err != nil {
panic(err)
}
}
This code initializes a Riak KV client and connects to the riak
container using the default port (8087
).
Implement creating pizzas in Riak KV
Replace the TODO
comment in the createPizza
function with the following code:
func createPizza(c *gin.Context) {
var pizza Pizza
if err := c.ShouldBindJSON(&pizza); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
key := riak.NewBinaryValue(pizza.ID)
obj := &riak.Object{
Bucket: "pizzas",
Key: key,
ContentType: "application/json",
Value: []byte(pizza.String()),
}
if _, err := client.StoreObject(obj); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "Pizza created"})
}
This code uses the ShouldBindJSON
method to deserialize the JSON payload into a Pizza
struct. It then creates a new riak.BinaryValue
with the pizza's ID and creates a new riak.Object
with the pizza's ID as the key, the pizzas
bucket, and the JSON payload as the value. Finally, it stores the object in Riak KV using the client.StoreObject
method.
Implement retrieving pizzas from Riak KV
Replace the TODO
comment in the getPizza
function with the following code:
func getPizza(c *gin.Context) {
id := c.Param("id")
key := riak.NewBinaryValue(id)
obj, err := client.FetchObject(&riak.FetchOptions{
Bucket: "pizzas",
Key: key,
})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Pizza not found"})
return
}
var pizza Pizza
if err := obj.GetValue(&pizza); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, pizza)
}
This code retrieves the pizza’s ID from the URL parameter, creates a new riak.BinaryValue
with the pizza's ID as the key, and fetches the object from Riak KV using the client.FetchObject
method. If the object is not found, it returns a 404 error. Otherwise, it deserializes the object's value into a Pizza
struct and returns it as a JSON response.
Run the application
Open a terminal window, navigate to the pizza-api
directory, and run the following command:
docker-compose up
This will start the Riak KV and Golang containers and expose the API on port 8080.
Test the API
Open a new terminal window and use the following commands to test the API:
# Create a new pizza
curl -X POST -H "Content-Type: application/json" -d '{"id": "1", "name": "Margherita", "crust": "Thin", "size": "Medium"}' http://localhost:8080/pizza
# Retrieve the pizza by ID
curl http://localhost:8080/pizza/1
The first command creates a new pizza with the ID 1
, the name Margherita
, the crust Thin
, and the size Medium
. The second command retrieves the pizza by its ID and returns its details as a JSON response.
Conclusion
In this blog post, we demonstrated how to integrate Riak KV with a simple Gin HTTP API using Docker Compose. We created an API that allows users to create their favorite pizza and retrieve it. We used the `riak-go-client` library to connect to Riak KV, store and retrieve objects, and demonstrated how to use the library in a Gin handler. By following this tutorial, you can now create your own distributed, fault-tolerant APIs using Riak KV and Gin.