All the code's that you will see in this post you can check here
To keep building our project, in this post we will build our service layer, domain model and our dependency injection container.
The structure from this project in this post will looks like this
📦hero-api-golang
┣ 📂cmd
┃ ┗ 📂http
┃ ┃ ┣ 📂handler
┃ ┃ ┃ ┣ 📜book.go
┃ ┃ ┃ ┗ 📜router.go
┃ ┃ ┗ 📜main.go
┣ 📂internal
┃ ┣ 📂container
┃ ┃ ┗ 📜container.go
┃ ┣ 📂domain
┃ ┃ ┗ 📂book
┃ ┃ ┃ ┗ 📜model.go
┃ ┗ 📂service
┃ ┃ ┗ 📜book.go
┣ 📜.gitignore
┣ 📜go.mod
┣ 📜go.sum
┗ 📜README.md
If you look to the structure will see a internal folder, in my projects i like to building my domain and services in the internal folder, when you name a package as internal, it can only be imported from its parent directory’s packages.
First we will define a domain folder and inside this folder we will create a folder to represent every domain that we will use in this project, in this case we will start a CRUD to a Book.
package book
type Book struct {
Title string
Author string
NumberPages int
}
This struct will represent our book model with information that will store inside our database.
It's a simple struct because i want to show you how to build a entire project, after understand the structure you can improve this domain like you want.
After that we will create our service layer, inside internal folder you can check the service folder, in this folder we will group all services necessary to build this project, you will see a lot of post's represent this layer like UseCase layer. If we decide to put any business rules in this project, the service layer is the place.
package service
import (
"github.com/maaarkin/hero-api-golang/internal/domain/book"
)
type BookService interface {
Save(book book.Book) (*book.Book, error)
FindAll() (*[]book.Book, error)
FindById(id uint64) (*book.Book, error)
Delete(id uint64) error
Update(book book.Book) error
}
type bookServiceImpl struct {
}
func NewBookService() BookService {
return bookServiceImpl{}
}
func (bookServiceImpl) Save(book book.Book) (*book.Book, error) {
return nil, nil
}
func (bookServiceImpl) FindAll() (*[]book.Book, error) {
return nil, nil
}
func (bookServiceImpl) FindById(id uint64) (*book.Book, error) {
return nil, nil
}
func (bookServiceImpl) Delete(id uint64) error {
return nil
}
func (bookServiceImpl) Update(book book.Book) error {
return nil
}
In this moment we just create the service layer, you can see that we return nil in all services because we will conect this layer with the repository layer in the next post, but we will build right now to understand how we will inject this service layer in our handlers.
And we will do that through the DI Container, see what i like to do in my projects to represent this "container"...
In the internal folder you can check the container package and the code is very simple.
package container
import (
"github.com/maaarkin/hero-api-golang/internal/service"
)
// TODO declare services/repositories/components, here
type Container struct {
Services Services
}
type Services struct {
BookService service.BookService
}
// Inject represent the starter of our IoC Container, here we will inject
// the necessary structs/functions that we need to build our project.
func Inject() Container {
//init services
bs := service.NewBookService()
services := Services{
BookService: bs,
}
return Container{
Services: services,
}
}
The container.go file is the place that we will make our "New()" functions to start any service, repository or component that we want.
And this container we will invoke in our main.go and pass this container through the layers and we will inject the dependencies that are necessary to our project start.
after that, our main.go will looks like this.
package main
import (
"github.com/maaarkin/hero-api-golang/cmd/http/handler"
"github.com/maaarkin/hero-api-golang/internal/container"
)
func main() {
di := container.Inject()
//delegate to start our Http Server
handler.StartServer(di)
}
And in the router.go we change this functions
func initHandlers(di container.Container) handlers {
return handlers{
//here we will inject the service to the handler
Book: NewBookHandler(di.Services.BookService),
}
}
func StartServer(di container.Container) {
//...
handlers := initHandlers(di)
//...
}
If you see the initHandlers(...) function you will see that we pass the container and in the moment that we start our BookHandler we pass the service to the NewBookHandler(di.Services.BookService).
That way, in the handler we can access the BookService interface that represent the service layer.
In this moment we know how to create our domain and service layer and understand how to start our components and pass the information in the project.
In the next post we will improve our handler to make the CRUD functions and access the service layer.
Top comments (5)
Hey man, let me ask you something, in case we'd have a database connection, where should be the correct place to put the driver of connections? thanks
there's no "correct place", but i like to put in internal/db to be more easy to understand
Thanks bro, I am trying to follow your boilerplate to build something. One more question will you continue this series?
yes, i'm changed to another company, then i'm need some time to study the new stack. But i will finish this post
Cool, I'm waiting for. Good luck bro :)