Skip to content

How to refactor a Backend API

Timothy Poon edited this page Apr 12, 2022 · 3 revisions

Here is an example PR which performs a refactor, along with some other changes.

You will most likely be assigned a single function, such as db#DB.GetUser, to refactor into a handler function.

1. Getting started

The first step you will take is likely to simply copy-paste the old function into the corresponding handler file, or creating one if it does not exist (e.g. db/program.go -> handler/program.go). First, the parameter of the method should be swapped from c echo.Context to cc echo.Context. Near the start of the function, add the line c := cc.(*db.DBContext). If you are creating a new file, make sure the package name is handler and that you are importing "github.com/uclaacm/teach-la-go-backend/db".

2. Add new CRUD functions, if necessary

Currently, all the CRUD functions (create, retrieve, update, delete) are housed within db/db.go. If the CRUD function does not exist already, it should be added here. In the future, these will be moved out into separate files, but since those are undergoing refactors it is easier to keep them all together.

These CRUD functions generally involve a single firebase call, as well as some basic error handling. For example:

func (d *DB) CreateProgram(ctx context.Context, p Program) (Program, error) {
	newProg := d.Collection(programsPath).NewDoc()
	p.UID = newProg.ID
	if _, err := newProg.Create(ctx, p); err != nil {
		return p, err
	}

	return p, nil
}

3. Replace firebase API calls

Next, you will want to replace any direct calls to the firebase API present in the function with the TLADB equivalents. There's no simple 100% consistent method to find these, but here is an example from the above PR:

	doc, err := d.Collection(usersPath).Doc(c.QueryParam("uid")).Get(c.Request().Context())
	if err != nil {
		return c.String(http.StatusNotFound, err.Error())
	}
	if err := doc.DataTo(&resp.UserData); err != nil {
		return c.String(http.StatusInternalServerError, err.Error())
	}

The line which actually interfaces with firebase is the first, while the rest just checks for errors and moves the data from firebase into our own struct (resp.UserData). The replacement code would be:

	user, err := c.LoadUser(c.Request().Context(), c.QueryParam("uid"))
	resp.UserData = user

The first line essentially performs the same function as the old lines 1-4, while the second is equivalent to the rest.

Here are some common firebase functions in the codebase:

  • Collection
  • Doc
  • NewDoc
  • Set
  • Update
  • DataTo

4. Move tests

Tests should be moved from the corresponding files (e.g. db/program_test.go to handler/program_test.go). As we are moving tests to using a mock DB, the line d := db.OpenMock() should be included at the start of each test. Additionally, the call to the function being tested will need a small adjustment:

OLD: d.GetUser(c)

NEW:

handler.GetUser(&db.DBContext{
			Context: c,
			TLADB:   d,
		})