Creating API with Go using MVC & Service Layer Pattern

Introduction

Hello! I hope you have a good day.

This article will be a tutorial kind of article that you might wanted to follow through step by step. We will be making a Restful API using Go, we will be also using library such as Gin & Gorm. The service we will be building are basic CRUD services so we don’t have to bother about any other complex mechanism. Before we start building the service you might wanted to get some tools up and running :

  1. Go / Golang

    In this tutorial I am using Go 1.17 using any lower version should not be an issue since we will be only dealing with basic services.

  2. MySQL / PostgreSQL

    This is by your choice, any of it will do just fine. As for me I am using PostgreSQL.

  3. IDE

    Again! this will be up 2 u, Visual Studio Code is good enough for every day use. But if you got the access I would recommend using GoLand. If you are a student you could actually get a free one year JetBrains Subscription from the GitHub Education program check more here :

Concepts

About MVC

Some of you clicking this article might be already know what MVC is hence it is a really common pattern that used in development. Regardless, here is a little overview of what is MVC.

Model-View-Controller, commonly referred to as MVC, is a pattern used to help organize and structure code. Specifically, most code will be classified as a model, view, or controller.

From calhoun.io - Using MVC to Structure Go Web Applications

The quote above describe nicely what MVC is, in MVC we separate our code into 3 main Concerns :

Model

In model we handle all the data related logic that users work with, This typically means communicating with the database. an example will be we have a service that register our users who signup and interacts with our website in certain way. In the Model we will make a type User struct {...} that will help us to map the data in database with our application. This will also help us to query create, update, delete from our databases.

In our application we will use Gorm that will help us creating the Models and also interacting with databases.

View

View are responsible with handling rendering and displaying data, usually view are handle a page such as HTML. In the real scenario language and framework will have different way handling views. And in MVC using view are optional this will suit our use case on making an API since we will not have any view.

Controller

Controller is the middleman, it act as an interface between Model and View Components. it will accept the incoming request, get the data from model, and prepare the data for the views. It sounds neat and easy to do on paper but on the reality controller usually became really coupled since there are so many business logic that are done inside the controller this happen because not only we put flow of business inside the controller, we also make the controller interact directly with the Models, in some cases the model will query the data and the process is done.

But in a complex business logic there might be multiple query that needs to be done and there are also post-processing after data from the model are acquired. To solve this issue we will introduce the concept of Service Layer Pattern that will be discussed in greater detail in next section.

About Service Layer Pattern

Service Layer Pattern are originally taken from Patterns of Enterprise Application Architecture by Martin Fowler, with Dave Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, and Randy Stafford.

Defines an application’s boundary with a layer of services that establishes a set of available operations and coordinates the application’s response in each operation.

A Service Layer defines an application’s boundary [Cockburn PloP] and its set of available operations from the perspective of interfacing client layers. It encapsulates the application’s business logic, controlling transactions and coor-dinating responses in the implementation of its operations.

Taken From - https://martinfowler.com/eaaCatalog/serviceLayer.html

Simply what service layer does is encapsulate common logic or interaction into a ‘service’, so that the logic can be easily accessed across multiple business logic and it will also reduce duplication. Example :

Without using service layer pattern, you might write all your code & logic in the controller starting from getting data from the database, till creating the loops to add the required attributes, this will led to coupling and duplication problems. But using service layer pattern we could encapsulate our logic into smaller parts and make it easily reusable.

The Project

Can’t lie it is pretty boring when it came to creating a common API like Todo List, Animal Database, Sales, Schools, you name it. So to avoid that in this project we will be making MMORPG matchmaking API. The case: in MMORPG it is really common to see player that are looking for ‘Hunting Parties’ to do quest or fight bosses together. If you ever played one and tries to look for a parties it is a really tedious job. You need to constantly post what are you looking for and you also need to keep an eye on the little chat bar so you don’t miss any of the opportunities, sometimes you might just ended up wasting time and having a headache looking at the chat bar.

image-20211010231829996

To solve this issue, I think that a dedicated matchmaking sites will do the job well. In this project we will create a basic setup of our Matchmaking API.

Step 0 - Setting Up The Directory

In this step we will be setting up our directory, this directory will be equivalent to package. I will be plotting our code into per function group simply we will group controller with controller and model with models. here is what it looks like :

image-20211011225925212

First there are the main part: Controller & Model this 2 part we took from the original MVC, but notice here that we don’t have any views. Also there is services directory that will be used to put our services, there are 2 other directory which the config and the routers, in config we will be placing our application config such as database information or maybe application versioning, in routers we will be placing our endpoints if you think about it routers seems like an alternative of view since view = return / render data & router = return data.

Using MVC does not mean that you need to strictly only have Models, View, and Controller in the end MVC is just an architectural pattern that supposed to help you shape your programs you can modify it to suite your needs but still there are some common and best practices exists that you might wanted to consider.

To dig deeper about how you should structure a MVC application, checkout [1] in the references section.

Step 1 - Preparing & Installing Modules

We will be installing the modules using Go Modules, to initiate the project go to your root project directory and type:

go mod init github.com/<your_github_name>/game-matchmaking

This will initiate our go module, next is to install the dependencies:

go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm

After installing these 2 modules your go.mod file should look something like this:

module github.com/arifluthfi16/game-matchmaking

go 1.17

require (
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/gin-gonic/gin v1.7.4 // indirect
	github.com/go-playground/locales v0.14.0 // indirect
	github.com/go-playground/universal-translator v0.18.0 // indirect
	github.com/go-playground/validator/v10 v10.9.0 // indirect
	github.com/golang/protobuf v1.5.2 // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/jinzhu/now v1.1.2 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/leodido/go-urn v1.2.1 // indirect
	github.com/mattn/go-isatty v0.0.14 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/ugorji/go/codec v1.2.6 // indirect
	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
	golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
	golang.org/x/text v0.3.6 // indirect
	google.golang.org/protobuf v1.27.1 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
	gorm.io/gorm v1.21.16 // indirect
)

For now our modules installation is completed, the final step that you need to do is to push the project to your github this step is optional but it is recommended, you could learn how to push your project into github from here : https://docs.github.com/en/github/importing-your-projects-to-github/importing-source-code-to-github/adding-an-existing-project-to-github-using-the-command-line

After you finished pushing your project into github, it should have look something like this:

image-20211012000013146

Step 2 - Setup Models & Databases

Before we dive in creating the models using GORM, first we need to analyze and create a requirement for the system / feature that we are going to create. In this case we will be making the room system where user could create, join, and search a room. The party room will have certain limitation such as maximum number of players and minimum level. To track which player that inside the room we need a N-N relation, we will call the relation as ‘match’. With that in mind here is the schema that we will be working with :

mvcfinal

From the schema above, we will be creating our models. we will be creating 3 models :

Creating Models

/model/player.go

package model

import (
	"gorm.io/gorm"
	"time"
)

type Player struct {
	Username	string			`gorm:"primaryKey;"`
	PlayerLevel	int
	CreatedAt 	time.Time
	UpdatedAt 	time.Time
	DeletedAt 	gorm.DeletedAt 	`gorm:"index"`
	Matches		[]Match			`gorm:"foreignKey:PlayerUsername"`
	Room		Room			`gorm:"foreignKey:OwnerUsername"`
}

/model/room.go

package model

import (
	"database/sql"
	"gorm.io/gorm"
)

type Room struct {
	gorm.Model
	Title 			string
	Description 	sql.NullString
	Game			string
	MaxPlayer 		int
	IsActive 		bool
	GameMode		string
	MinLevel		int
	Matches			[]Match			`gorm:"foreignKey:RoomID"`
	OwnerUsername	string
}

/model/match.go

package model

import (
	"gorm.io/gorm"
)

type Match struct {
	gorm.Model
	PlayerUsername	string
	RoomID			string
}

Setting up Connections & Migration

After we finished creating the models, the next step is to create a database connection and execute the migration. To create a service that handle database connections we will be using dependency injection this will make us easier to work with dependencies and also testing later. Here is an amazing article by Alex Edwards on managing databases connection using dependency injection.

Testing Gorm

First of all in the root directory create /main.go

package main

import (
	"fmt"
	"gorm.io/gorm"
)

type Server struct {
	db *gorm.DB
}

func main(){
	fmt.Println("Gorm Is Installed")
}

Then simply go to your console and type

go run main.go

If there is no error in the console, then it means your GORM is installed and you are ready to go.

Preparing Model Registry

Create a file /model/index.go

package model

type Model struct {
	Model interface{}
}

func RegisterModels() []Model {
	return []Model{
		{Model: Room{}},
		{Model: Player{}},
		{Model: Match{}},
	}
}

In this model registry, we created a central point where we can access all the models. This will help us later on when doing migration.

Preparing Database Service

In this service we will be creating our config, create connection, and also migration scripts.

create a file called /services/db/db.go

package db

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/model"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"log"
)

type DBConfig struct {
	DBHost string
	DBUser string
	DBName string
	DBPort string
	DbPass string
}

func LoadDB(config DBConfig) *gorm.DB {
	var err error
	conStr := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Jakarta",
		config.DBHost, config.DBUser, config.DbPass, config.DBName, config.DBPort,
	)
	db, err := gorm.Open(postgres.Open(conStr), &gorm.Config{})
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println("!! Database Connection Loaded !!")
	return db
}

func DBMigrate (db *gorm.DB){
	var err error
	for _,model := range model.RegisterModels(){
		err = db.Debug().AutoMigrate(model.Model)
		if err != nil {
			log.Fatal(err)
		}
	}
	fmt.Println("!! Database Migration Succeed !!")
}

In this file we created a service that covers variety of database action:

  1. We createdDBConfig struct to make easier parameter passing later
  2. We created LoadDB() that return *gorm.DB this function will help us to create connection to the databases.
  3. Finally we create DBMigrate that accepts a database instance this function will be used later to execute migration. in this function we called our model.RegisterModels() we created earlier that will return an array of model to be processed down the migration.

Using The Connections

Back to our /main.go file, we will be adding the services to the main function.

package main

import (
	"github.com/arifluthfi16/game-matchmaking/services/db"
	"gorm.io/gorm"
)

type Server struct {
	db *gorm.DB
}

func main(){
	DBConfig := services.DBConfig{
		DBHost: "localhost",
		DBUser: "postgres",
		DBName: "game_matchmaking",
		DBPort: "5432",
		DbPass: "<<your db pass>>",
	}
	app := Server{db: services.LoadDB(DBConfig)}
	services.DBMigrate(app.db)
}

In this file, firstly we created a database config using the services.DBConfig you can make this extra safe by using .env if you wanted to. Then we created an instance of Server and assigning db with services.LoadDB(). This way we have our database connection established.

To run our migration simply call services.DBMigrate() and pass our app.db as parameters. if you see

...
!! Database Migration Succeed !!

Then it means that you successfully connected to database and your migration is successfully executed!

Please note that you do not have to run the migration every time you start the server. after running the migration I commented the code so it will not run later until it needed. for the sake of simplicity this will be enough for now.

Here is the visualization of the successful migration!

Finished Migration

After we got our data setup ready, next step will be making the actual business process and services. Before continuing let’s create a checkpoint here by committing & pushing it to the GitHub repo.

git add .
git commit -m "feat(db): established database model, connection & migration"
git push origin main

I follow Angular Contributing Guide to struct the commit message. and for the sake of this project we will not be creating any branch.

Step 3 - Creating Services

First we need to define what business process that we wanted to create here, this project we will limit our business process to :

  1. Create a player
  2. Create a room
  3. Other player can join room
  4. Room owner can create minimum level limitation.

Please note here that we will not implement any authentication module so for now we will pass the literal player username when doing a request.

Creating Player Services

In player service we will be making 2 files:

services/player/index.go

package player

import (
	"gorm.io/gorm"
)

type PlayerService struct {
	DB *gorm.DB
}

The index will be used to put the basic struct and other struct definition.

services/player/create.go

package player

import (
	"github.com/arifluthfi16/game-matchmaking/model"
)

func (s PlayerService) CreateOne (username string) (model.Player, error) {
	player := model.Player{
		Username:    username,
		PlayerLevel: 1,
	}

	if err := s.DB.Create(&player).Error; err != nil {
		return player, err
	}
	return player, nil
}

In the create module is where we will put all creational function, like create one, create many, or other that have similar function like maybe create bulk or create by provided certain parameters.

Creating Room Services

Same as the player service, we will be creating index.go first

services/room/index.go

package room

import (
	"gorm.io/gorm"
)

type RoomService struct {
	DB *gorm.DB
}

services/room/create.go

package room

import (
   "database/sql"
   "github.com/arifluthfi16/game-matchmaking/model"
)

func (s RoomService) CreateOne (ownerUsername string, title string, description string) (model.Room, error) {
   room := model.Room{
      Title:         title,
      Description:   sql.NullString{
         String: description,
      },
      Game:          "Warframe",
      MaxPlayer:     4,
      IsActive:      true,
      GameMode:      "Normal",
      MinLevel:      1,
      OwnerUsername: ownerUsername,
   }

   if err := s.DB.Create(&room).Error; err != nil {
      return room, err
   }
   return room, nil
}

In general this script is quite the same with the creational script before, so you should be fine.

Creating Match Services

In this service we will be creating 3 files, and same as before we will be starting on the index.

/services/match/index.go

package match

import (
	"gorm.io/gorm"
)

type MatchService struct {
	DB *gorm.DB
}

services/match/create.go

package match

import (
	"github.com/arifluthfi16/game-matchmaking/model"
)

func (s MatchService) PlayerJoinRoom (username string, roomID uint) (model.Match, error){
	match := model.Match{
		PlayerUsername: username,
		RoomID:         roomID,
	}
	if err := s.DB.Create(&match).Error; err != nil {
		return match, err
	}
	return match, nil
}

services/match/find.go

package match

import (
	"github.com/arifluthfi16/game-matchmaking/model"
)

func (s MatchService) FindAll () ([]model.Match, error) {
	var matches []model.Match
	result := s.DB.Find(&matches)
	return matches, result.Error
}

func (s MatchService) FindUserInRoom (username string, roomID uint) (model.Match, error) {
	var match model.Match
	result := s.DB.Where(&model.Match{
		PlayerUsername: username,
		RoomID:         roomID,
	})
	return match, result.Error
}

finally a find services, the name explains itself. in this service we create modules to find data. after all the services we need is completed next step will be mapping it to our controller and routers. Do not forget to push your services updated to your github repo.

Step 4 - Mapping the Controller

In part we will be mapping our services to the business process. but first we will be creating a template for our error and success response.

Responses

controller/response.go

package controller

type ErrorResponse struct {
	Msg string
	Err error
}

type SuccessResponse struct {
	Status		bool	`json:"status"`
	Msg 		string 	`json:"msg"`
}

This will be used as a formatter for our error and success API response.

Player Controller

controller/player.go

package controller

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/services"
	"github.com/gin-gonic/gin"
	"net/http"
)

type PlayerController struct {}

type createPlayerInput struct {
	Username string `json:"username" binding:"required"`
}

func (controller PlayerController) CreatePlayer(c *gin.Context) {
	var input createPlayerInput
	if err := c.ShouldBindJSON(&input); err != nil {
		var response = ErrorResponse{
			Msg: "Validation Error",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	player, err := services.PlayerService.CreateOne(input.Username)
	if  err != nil {
		var response = ErrorResponse{
			Msg: "Failed to Create Player",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	var responseString = fmt.Sprintf("Successfully created a new player with username : %s", player.Username)
	var response = SuccessResponse{
		Status: true,
		Msg:    responseString,
	}
	c.AbortWithStatusJSON(http.StatusOK, response)
}

Explanation:

package controller

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/services"
	"github.com/gin-gonic/gin"
	"net/http"
)

type PlayerController struct {}
var playerService = services.PlayerService

In in this part we called some dependencies, and also the service that we created earlier. We also wrap our player service into playerService variable.

type createPlayerInput struct {
	Username string `json:"username" binding:"required"`
}

in this part we define our input structure that will be used when users send request via our endpoint later.

func (controller PlayerController) CreatePlayer(c *gin.Context) {
	var input createPlayerInput
	if err := c.ShouldBindJSON(&input); err != nil {
		var response = ErrorResponse{
			Msg: "Validation Error",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	player, err := services.PlayerService.CreateOne(input.Username)
	if  err != nil {
		var response = ErrorResponse{
			Msg: "Failed to Create Player",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	var responseString = fmt.Sprintf("Successfully created a new player with username : %s", player.Username)
	var response = SuccessResponse{
		Status: true,
		Msg:    responseString,
	}
	c.AbortWithStatusJSON(http.StatusOK, response)
}

Finally the controller itself! here we handle our input, input validation, the player creation, and response management. Whoa that is one long module, just a little bit more!

Room Controller

controller/room.go

package controller

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/services"
	"github.com/gin-gonic/gin"
	"net/http"
)

type RoomController struct {}

type createRoomInput struct {
	OwnerUsername	string	`json:"owner_username"`
	Title			string	`json:"title"`
	Description		string	`json:"description"`
}

func (controller RoomController) CreateRoom (c *gin.Context) {
	// * Input Validation
	var input createRoomInput
	if err := c.ShouldBindJSON(&input); err != nil {
		var response = ErrorResponse{
			Msg: "Validation Error",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	// * Create Room into DB
	room, err := services.RoomService.CreateOne(input.OwnerUsername, input.Title, input.Description)
	if  err != nil {
		var response = ErrorResponse{
			Msg: "Failed to Create Player",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	// * Success Response
	var responseString = fmt.Sprintf("Successfully created a new room with owner : %s", room.OwnerUsername)
	var response = SuccessResponse{
		Status: true,
		Msg:    responseString,
	}
	c.AbortWithStatusJSON(http.StatusOK, response)
}

Match Controller

controller/match.go

package controller

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/services"
	"github.com/gin-gonic/gin"
	"net/http"
)

type MatchController struct {}

type createMatchInput struct {
	Username 	string 	`json:"username" binding:"required"`
	RoomID		int		`json:"room_id" binding:"required"`
}

func (controller MatchController) JoinMatch (c *gin.Context) {
	// * Input Validation
	var input createMatchInput
	if err := c.ShouldBindJSON(&input); err != nil {
		var response = ErrorResponse{
			Msg: "Validation Error",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	// * PLayer Join Room
	isPlayerJoined, err := services.MatchService.FindUserInRoom(input.Username, uint(input.RoomID))
	if  err != nil {
		var response = ErrorResponse{
			Msg: "Failed to check player",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	fmt.Println(isPlayerJoined)
	match, err := services.MatchService.PlayerJoinRoom(input.Username, uint(input.RoomID))
	if  err != nil {
		var response = ErrorResponse{
			Msg: "Failed to Create Player",
			Err: err,
		}
		c.AbortWithStatusJSON(http.StatusOK, response)
		return
	}

	var responseString = fmt.Sprintf("Successfully joined room %s", string(match.RoomID))
	var response = SuccessResponse{
		Status: true,
		Msg:    responseString,
	}
	c.AbortWithStatusJSON(http.StatusOK, response)
}

With this done our controller are completed and the final step is just to setup our router.

Step 5 - Router & More Server Setup

if you are not already created a routers folder, create one. The first we will be creating our base route grouping: Player, Room, & Match that extends a certain interfaces. Then we will create a route loader to load all the routes.

Route Groups

Before creating the route groups, we need to create an interface for our route group to extends.

routers/index.go

package routers

import "github.com/gin-gonic/gin"

type RouterInterface interface {
	Route(*gin.Engine)
}

We create the RouterInterface that have the Route() function so that later it will be loop able to load.

Player

routers/player.go

package routers

import (
	"github.com/gin-gonic/gin"
	"github.com/arifluthfi16/game-matchmaking/controller"
)

type Player struct {}

func (p *Player) Route (route *gin.Engine){
	router := route.Group("/player")
	Controller := controller.PlayerController{}

	router.POST("/", Controller.CreatePlayer)
}

As you can see above, we implement the Route() function so that the this Player routers is implementing the RouterInterface this will apply to others route group as well.

Room

routers/room.go

package routers

import (
	"github.com/gin-gonic/gin"
	"github.com/arifluthfi16/game-matchmaking/controller"
)

type Room struct {}

func (p *Room) Route (route *gin.Engine){
	router := route.Group("/room")
	Controller := controller.RoomController{}

	router.POST("/", Controller.CreateRoom)
}

Match

routers/match.go

package routers

import (
	"github.com/gin-gonic/gin"
	"github.com/arifluthfi16/game-matchmaking/controller"
)

type Match struct {}

func (p *Match) Route (route *gin.Engine){
	router := route.Group("/match")
	Controller := controller.MatchController{}

	router.POST("/join", Controller.JoinMatch)
}

Route Loader

We will modify our /routers/index.go we will create a loader that will utilize the route that we have been created.

package routers

import "github.com/gin-gonic/gin"

type RouterInterface interface {
	Route(*gin.Engine)
}

type RouteLoader struct{}

func (loader RouteLoader) LoadRoutes() []RouterInterface {
	player 	:= new (Player)
	room 	:= new (Room)
	match 	:= new (Match)
	return []RouterInterface{
		player,
		room,
		match,
	}
}

The LoadRoutes() will initiate and return an array of the RouterInterface that will be processed later for creating routes.

Server Setup

Back to our main.go we will be do some tidying up and create a few modules to utilize our routes.

package main

import (
	"fmt"
	"github.com/arifluthfi16/game-matchmaking/routers"
	"github.com/arifluthfi16/game-matchmaking/services"
	"github.com/arifluthfi16/game-matchmaking/services/db"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type Server struct {
	db *gorm.DB
	Router *gin.Engine
}

func (server *Server) InitServer(){
	server.Router = gin.Default()
	var Router = routers.RouteLoader{}
	for _, routes := range Router.LoadRoutes(){
		routes.Route(server.Router)
	}
}

func (server *Server) InjectDB(){
	services.InjectDBIntoServices(server.db)
}

func (server *Server) Run(){
	fmt.Println("Rise and shine! 🌞🌞🌞")
	fmt.Println("Listening on port : 5050")
	server.Router.Run("127.0.0.1:5050")
}

func main(){
	DBConfig := db.DBConfig{
		DBHost: "localhost",
		DBUser: "postgres",
		DBName: "game_matchmaking",
		DBPort: "5432",
		DbPass: "<<your password>>",
	}
	app := Server{db: db.LoadDB(DBConfig)}
	app.InjectDB()
	app.InitServer()
	app.Run()
}

Here we split our process into different module, as you can see by separating our process we could get a clear main function. Final step will be testing our application. To run the application you simply in the root file.

go run main.go

image-20211024002303825

If there is no problem you should see output similar to this.

Testing the Endpoint

To test the endpoint i will be using Postman.

Create Player

image-20211024002752537

To test the create player we simply need to send parameter username to the endpoint [POST] localhost:5050/player/

Create Room

image-20211024003920151

Failed request example:

image-20211024004041572

Match

image-20211024004831514

Its done! finally! Do not forget to push your finished project to github!

Conclusion

Yes there are definitely a lot of modules and implementation that we can improve on, but for the sake of this project I wanted to focus on utilizing MVC and Service Layer pattern. I wanted to show how we can use MVC and Service layer to structure the project we are working on.

And as you can see by implementing those two methods we can produce a much cleaner and also more maintainable code, but still there is drawbacks such as there is a lot more package and modules that we need to define. But aside the extra modules that we need to create for a long term project it will be worth it.

In the next project we will dockerize a golang project and deploy it to google cloud!

Thanks a lot for reading this long article, any feedback will be very much appreciated! Thanks for reading!

Special Thanks

Special Thanks to Afi for supports during the writing process and debugging the pipeline. <3

Source Codes

You can find the source code of this article here https://github.com/arifluthfi16/game-matchmaking

More Sources

  1. https://www.calhoun.io/using-mvc-to-structure-go-web-applications/

  2. https://softwareengineering.stackexchange.com/questions/211688/use-a-service-layer-with-mvc

  3. https://stackoverflow.com/questions/31180816/mvc-design-pattern-service-layer-purpose

  4. https://medium.com/stackavenue/why-to-use-service-layer-in-spring-mvc-5f4fc52643c0