03 - Application Architecture
Tags: go, architecture, gin, tutorial
Published: 2020-10-08 15:29:13

Last time we covered adding routes to our application. These routes serve as the entry point to our application from the outside world. This time around, we'll go over the layers of our application! 


If you have any confusion about file structure or code, go to the [Github repository](https://github.com/JacobSNGoodwin/memrizr) and checkout the branch for this lesson!

If you prefer video, check out the video version below!

{% youtube GWT7pU20jnQ %}

### Application Architecture

A summary of the architecture is depicted below. This architecture is heavily influenced by [go_clean_arch](https://github.com/bxcodec/go-clean-arch) 👍🏼🎉. Each box represents an application layer.

![Application Architecture](https://dev-to-uploads.s3.amazonaws.com/i/n8qbildzd5f9jgh4fmvt.png)

Incoming HTTP requests are parsed and validated by the _handler_ layer. The handler layer calls the _service_ layer's methods, which in turn accesses the _repository_, which uses _data sources_ (persistence, data storage, etc.). Each layer depends on a "concrete implementation" of the layer to its right.

All of these layers can "work with" and pass models, defined in the _model_ layer, to and from each other. These models hold the fundamental data properties, errors, and interfaces of the application. In some architectures, there may be a distinction between "domain models" and "data models," which will require methods for transforming the data models into domain models. I found that to be overkill for the current application, though this may be something to consider for your application.

### Architecture Benefits

This architecture lends itself well to unit testing, though this is by no means the only such architecture. Each layer can define an expectation of what each layer to its right must "implement." Any actual, or "concrete implementation," of a layer must conform to these expectations. We define these expectations in Go, and many other languages, by defining _interfaces_. We can then test the application layers separately by "mocking" the responses from these interfaces. 

As an example, we're going to create a _user_ service. The service layer handles the application's logic. One functionality we need is to get a user's information, such as their name and email address. Therefore, the _user_ service expects to be able to get a user from the database. This means that any repository that interacts with the _user_ service should have a method for getting users (e.g., _FindUserByID_ for getting a user based on their ID). We define the signature of this method as part of the _UserRepository interface_ (example below).

Yet as far as the _user_ service is concerned, it does not care how _FindUserByID_ is implemented inside of the repository layer. The repository layer could implement this in numerous ways, and can even use different data sources (MySQL instead of Postgres, for example).  

Some projects may be very strict about unit testing each layer, and others may "integrate" various layers. As an example, the handler and service layers may be tested together instead of separately, or even combined into a single layer. In my opinion, the most critical layer that should absolutely be unit tested is the _service_ layer. In this application, we will not be creating interfaces for the _data sources_.

Now let's create our _user_ model, and add our first interfaces and errors to to the model layer! Next time, we'll implement simplified versions of the various layers for getting a user's personal information. 

### Create _model_ package

Let's add a model folder to hold our _user_ model along with our application errors and interfaces. Inside of this folder, folder, create the following files.

![Model Layer Files](https://dev-to-uploads.s3.amazonaws.com/i/towj05r0zg1ps8n9ft45.PNG)

#### user.go

Insider of _user.go_, we add a struct with the properties our user model will hold. We import the uuid library to define user IDs of type UUID (you may need to `go get "github.com/google/uuid"`). 

We also add _struct tags_ between back ticks for each field. These fields provide a mapping for the name of the struct field in Go to the name of the field in the database and the name of the field in any JSON response. Note that the _password_ uses a json value of "-". This prevents the password from being returned in any JSON response.

```go
package model

import (
	"github.com/google/uuid"
)

// User defines domain model and its json and db representations
type User struct {
	UID      uuid.UUID `db:"uid" json:"uid"`
	Email    string    `db:"email" json:"email"`
	Password string    `db:"password" json:"-"`
	Name     string    `db:"name" json:"name"`
	ImageURL string    `db:"image_url" json:"imageUrl"`
	Website  string    `db:"website" json:"website"`
}
```

#### interfaces.go

In this file, we define of our expectations for each layer. For now, we'll only add the interfaces that we'll use in the next tutorial. The _handler_ will expect to access a method that either returns a User or an error. The same is true for the repository layer. While in this case there is a one-to-one correspondence in the signatures of the _service_ and _repository_ layers, this is not always the case. For example, a service might need to access multiple repositories in a single service method, or require access to other helper or utility functions, and apply logic to produce its response. 

```go
package model

import "github.com/google/uuid"

// UserService defines methods the handler layer expects
// any service it interacts with to implement
type UserService interface {
	Get(uid uuid.UUID) (*User, error)
}

// UserRepository defines methods the service layer expects
// any repository it interacts with to implement
type UserRepository interface {
	FindByID(uid uuid.UUID) (*User, error)
}
```

#### errors.go

In this file, we do the following. 

1. Create a custom _Error_ struct with JSON struct tags for sending JSON responses from our API with "type" and a "message" fields. 
2. Define all of the error types we expect to return (Authorization, BadRequest, ..., PayloadTooLarge). At the bottom of the file you'll see factories for creating each error type with a custom message. These factories can be called in the various application layers.
3. An _Error()_ receiver method. This allows our custom Error to implement the Golang _error_ interface.
4. A _Status()_ receiver method which is used to get the HTTP status of the error as an int. This is useful when sending responses in handlers. 
5. A _func Status(err error) int_ function. This allows us to determine if an error is a model.Error (i.e., the one we're defining right now). If it is, the function returns the HTTP Status code in 4. If not, it returns an Internal Service Error (500) status code.

```go
package model

import (
	"errors"
	"fmt"
	"net/http"
)

// Type holds a type string and integer code for the error
type Type string

// "Set" of valid errorTypes
const (
	Authorization   Type = "AUTHORIZATION"   // Authentication Failures -
	BadRequest      Type = "BADREQUEST"      // Validation errors / BadInput
	Conflict        Type = "CONFLICT"        // Already exists (eg, create account with existent email) - 409
	Internal        Type = "INTERNAL"        // Server (500) and fallback errors
	NotFound        Type = "NOTFOUND"        // For not finding resource
	PayloadTooLarge Type = "PAYLOADTOOLARGE" // for uploading tons of JSON, or an image over the limit - 413
)

// Error holds a custom error for the application
// which is helpful in returning a consistent
// error type/message from API endpoints
type Error struct {
	Type    Type   `json:"type"`
	Message string `json:"message"`
}

// Error satisfies standard error interface
// we can return errors from this package as
// a regular old go _error_
func (e *Error) Error() string {
	return e.Message
}

// Status is a mapping errors to status codes
// Of course, this is somewhat redundant since
// our errors already map http status codes
func (e *Error) Status() int {
	switch e.Type {
	case Authorization:
		return http.StatusUnauthorized
	case BadRequest:
		return http.StatusBadRequest
	case Conflict:
		return http.StatusConflict
	case Internal:
		return http.StatusInternalServerError
	case NotFound:
		return http.StatusNotFound
	case PayloadTooLarge:
		return http.StatusRequestEntityTooLarge
	default:
		return http.StatusInternalServerError
	}
}

// Status checks the runtime type
// of the error and returns an http
// status code if the error is model.Error
func Status(err error) int {
	var e *Error
	if errors.As(err, &e) {
		return e.Status()
	}
	return http.StatusInternalServerError
}

/*
* Error "Factories"
 */

// NewAuthorization to create a 401
func NewAuthorization(reason string) *Error {
	return &Error{
		Type:    Authorization,
		Message: reason,
	}
}

// NewBadRequest to create 400 errors (validation, for example)
func NewBadRequest(reason string) *Error {
	return &Error{
		Type:    BadRequest,
		Message: fmt.Sprintf("Bad request. Reason: %v", reason),
	}
}

// NewConflict to create an error for 409
func NewConflict(name string, value string) *Error {
	return &Error{
		Type:    Conflict,
		Message: fmt.Sprintf("resource: %v with value: %v already exists", name, value),
	}
}

// NewInternal for 500 errors and unknown errors
func NewInternal() *Error {
	return &Error{
		Type:    Internal,
		Message: fmt.Sprintf("Internal server error."),
	}
}

// NewNotFound to create an error for 404
func NewNotFound(name string, value string) *Error {
	return &Error{
		Type:    NotFound,
		Message: fmt.Sprintf("resource: %v with value: %v not found", name, value),
	}
}

// NewPayloadTooLarge to create an error for 413
func NewPayloadTooLarge(maxBodySize int64, contentLength int64) *Error {
	return &Error{
		Type:    PayloadTooLarge,
		Message: fmt.Sprintf("Max payload size of %v exceeded. Actual payload size: %v", maxBodySize, contentLength),
	}
}
```

### Next Time

We now have the basics of our model layer scaffolded out which will allow us to write our first handler, service, and repository implementations. 

Thanks again for taking time to read! I hope you'll follow along with the series.