Reader small image

You're reading from  Protocol Buffers Handbook

Product typeBook
Published inApr 2024
PublisherPackt
ISBN-139781805124672
Edition1st Edition
Right arrow
Author (1)
Clément Jean
Clément Jean
author image
Clément Jean

Clément Jean is the CTO of Education for Ethiopia, a start-up focusing on educating K-12 students in Ethiopia. On top of that, he is also an online instructor (on Udemy, Linux Foundation, and others) teaching people about diff erent kinds of technologies. In both his occupations, he deals with technologies such as Protobuf and gRPC and how to apply them to real-life use cases. His overall goal is to empower people through education and technology.
Read more about Clément Jean

Right arrow

Implementing the Address Book in Go

From this chapter on, we are going to see a more practical use of Protobuf. In earlier chapters, we talked about syntax, how to carefully craft schema, and how to generate code from these schemas. We will now use the generated code in a programming language to create a local address book in which we can store and list contacts.

In this chapter, we’re going to cover the following main topics:

  • Interacting with most Protobuf constructs in Go
  • Writing/reading Protobuf encoded data to/from a file
  • Using Protobuf reflection to act on field data

By the end of the chapter, you will be able to interact with Protobuf-generated code in Go. You will understand how to use the generated code in your application to serialize and deserialize any kind of data.

Technical requirements

All the code that you will see in this chapter can be found in the directory called chapter7 in the GitHub repository (https://github.com/PacktPublishing/Protocol-Buffers-Handbook).

Disclaimer

This chapter will focus on the Go implementation of an address book. This is in no way a Go tutorial, so if you are not familiar with the language and do not want to go through this, you can find a Python implementation in the next chapter. However, I would still encourage you to skim through this chapter to be able to analyze the difference between the generated Go code and the Python code. You will see that Protobuf generates idiomatic code, and some concepts have different feels in different languages.

The project: address book

As mentioned in the introduction, this is a practical chapter. We are going to create a mini command-line interface (CLI) that lets us create and list contacts in an address book. Before we dive into the code, we will talk about the project itself and some of the architectural choices made in this project.

So, let’s talk about the project. This address book project was heavily influenced by the official tutorial in the Protobuf documentation (https://protobuf.dev/getting-started/gotutorial/). However, there are quite a few changes that I made to cover more Protobuf concepts. Here are the changes that I made:

  • Instead of adding only a person’s contact in the address book, we will also be able to add a company’s contact. This lets us use oneof because the contact will be either a company or a person.
  • While the CLI will not cover searching the contact by name (this is a challenge for you), we made sure that it would be possible...

Defining the schema

Starting a project involving Protobuf always starts by defining the schema. This is known as Schema-Driven Development (SDD). We essentially define the contract that needs to be fulfilled.

We are going to take a bottom-up approach to design this schema. We are going to start by defining what a Person contact looks like. As we saw in the description of the project, a person can have an email and multiple phone numbers. This looks like the following (proto/addressbook.proto):

message Person {
  //...
  string email = 1;
  repeated PhoneNumber phones = 2;
}

While the email is simply a string, the phone number will be represented by a nested message. This is because we want to be able to store the phone number and the type of phone number it is. As we know, the type of phone number can be mobile, work, or home. This means that we can represent this set of values with an enum. Knowing all of this, we now have the following:

message...

Boilerplate code

Let’s prepare some convenient helpers for the journey ahead. We know we will need writing/reading to/from files and that we will need to transform some arguments, passed as strings, into Go types. Let’s deal with the latter first since this is a very trivial task.

Converting string to enum values

We are going to create two functions: strToPhoneType and strToDepartment. They look similar since we are going to check the value of the string and derive an enum value from it. Let’s start with strToPhoneType.

We know that the PhoneType enum contains the values TYPE_HOME, TYPE_MOBILE, TYPE_WORK, and TYPE_UNSPECIFIED. TYPE_UNSPECIFIED is the default value of PhoneType since enums have 0 as their default value. Conveniently, Golang initializes variables with 0 values. Thus, we will simply check for the values of home, mobile, and work. If the string does not contain anything or a value that isn’t one of these, the phone type will be considered...

Adding entries

In this section, let’s focus on adding a Person or a Company contact to the AddressBook. We will start with the business logic (pkg) and then we will link the business logic to the CLI part of our application (cmd).

The business logic

We will first add a new file under pkg/addressbook called add.go. This will contain all the code related to the addition of contact. In this file, we will have two functions: AddPerson and AddCompany. These functions are similar; however, they receive different information as parameters. Let’s first talk about the similarities between these two functions.

The first similarity is that both functions take db as a parameter. Once again, we will use an interface to keep this code as generic as possible. This time, however, we will use the io.ReadWriter interface since we need both Read and Write functions. So, we have the following (pkg/addressbook/add.go):

func AddPerson(db io.ReadWriter, ...) error {
  ...

Listing entries

The business logic

The job left for CLI to be completed is rather small. We only need to read data from the database and display it on the standard output. Let’s start by reading the data from the database (pkg/addressbook/list.go):

import (
  "io"
)
func ListContacts(db io.Reader, ...) error {
  book, err := readFromDb(db)
  if err != nil {
    return err
  }
  //...
}

Next, we can order the list of contacts alphabetically. This was mostly done for testing purposes, but it could be used for more advanced features, such as filtering or paging. Here, we will simply focus on displaying all the contacts. The sorting looks like the following:

import (
  //...
 "sort"
)
func ListContacts(db io.Reader, w io.Writer, redact bool) error {
  //...
  names := make([]string, 0, len(book.Contacts))
  for name := range book.Contacts ...

Hiding sensitive data

The last thing that I want to add for this mini-project to be complete is to work with a field option. In our case, we will work with an option called debug_redact. This tells an application to redact the private information in the data. For us, this means hiding the phone numbers and email addresses.

Let’s start by adding the option to the fields we want to redact in our proto file:

message Person {
  //...
  message PhoneNumber {
    string number = 1 [debug_redact = true];
    Type type = 2;
  }
  string email = 1 [debug_redact = true];
  repeated PhoneNumber phones = 2;
}
message Company {
  //...
  message EmailAddress {
    string email = 1 [debug_redact = true];
    Department department = 2;
  }
  message PhoneNumber {
    string number = 1 [debug_redact...

Summary

In this chapter, we got more practical experience with Protobuf. We learned how to interact with Protobuf in Go. We touched upon as many Protobuf concepts as possible: maps, repeated, oneof, nested types, field options, and so on. Then, we learned how to serialize/deserialize data to/from a file. Finally, we linked everything to a CLI that has the add and list commands.

In the next chapter, we will create the same project in Python. The overall goal is to learn how to interact with Protobuf in this new language but also to show you that serialized data in Go can be read in Python and inversely.

Challenge

Here are a few challenges that might be fun:

  • Implement a search by name. This should be pretty easy since we have a map of contacts.
  • Implement listing by starting letter. The contact list is already sorted so it should not take too long.
  • Implement paging where only a subset of contact is returned at a time. The design of this feature is not defined, just make sure that you can specify the number of contacts you want and that you get the first N contacts, then the next N, and so on.
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Protocol Buffers Handbook
Published in: Apr 2024Publisher: PacktISBN-13: 9781805124672
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Clément Jean

Clément Jean is the CTO of Education for Ethiopia, a start-up focusing on educating K-12 students in Ethiopia. On top of that, he is also an online instructor (on Udemy, Linux Foundation, and others) teaching people about diff erent kinds of technologies. In both his occupations, he deals with technologies such as Protobuf and gRPC and how to apply them to real-life use cases. His overall goal is to empower people through education and technology.
Read more about Clément Jean