DEV Community

Cover image for Add some data in memory
Marcos Filho
Marcos Filho

Posted on

Add some data in memory

All the code's that you will see in this post you can check here

After we structure our project to pass the service into the handler constructor, now we can access the service layer in our rest handler.

But our service layer does nothing at the moment, in this first time we will create our repository/persistence layer to persist the information in memory and we can access the information in the service layer, doing the same process that we do between service/handler now we will do between service/repository.

The repository layer will have the responsibility to communicate with the database (in-memory initially) and return only the domain code to the service layer. In our project, only the service can access the repository. Usually, I like to do this because it's easier to maintain the project. When you have multiple places accessing the repository being more difficult to track some problems in the code.

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
 ┃ ┣ 📂repository
 ┃ ┃ ┣ 📜book.go
 ┃ ┃ ┗ 📜book_inmemory.go
 ┃ ┗ 📂service
 ┃ ┃ ┗ 📜book.go
 ┣ 📜.gitignore
 ┣ 📜go.mod
 ┣ 📜go.sum
 ┗ 📜README.md
Enter fullscreen mode Exit fullscreen mode

Now you can see a new repository folder, here we will group the files that will represent our persistence layer to connect with our database and return a domain code to our service.

//book.go
package repository

import "github.com/maaarkin/hero-api-golang/internal/domain/book"

type BookStore interface {
    GetAll() (*[]book.Book, error)
    Get(id uint64) (*book.Book, error)
    Create(item book.Book) (uint64, error)
    Update(id uint64, item book.Book) (*book.Book, error)
    Delete(id uint64) error
}

Enter fullscreen mode Exit fullscreen mode

Here we will represent the default CRUD that we can see in any post in the internet using this interface.

In this first step we will store our data in memory for this reason we have a inmemory file in this folder.

//book_inmemory.go
package repository

import (
    "errors"
    "sync"

    "github.com/maaarkin/hero-api-golang/internal/domain/book"
)

var (
    keyInstance = uint64(3)

    books = map[uint64]book.Book{
        1: {Id: 1, Title: "Title 1", Author: "MarkMark", NumberPages: 101},
        2: {Id: 2, Title: "Title 2", Author: "MarkMark 2", NumberPages: 203},
    }
)

type bookStoreInMemory struct {
    mu sync.Mutex
}

func NewBookStoreInMemory() BookStore {
    return &bookStoreInMemory{}
}

func (store bookStoreInMemory) GetAll() (*[]book.Book, error) {
    m := make([]book.Book, 0)

    for _, value := range books {
        m = append(m, value)
    }

    return &m, nil
}

func (store bookStoreInMemory) Get(id uint64) (*book.Book, error) {
    book, has := books[id]
    if !has {
        return nil, errors.New("No book in database")
    }
    return &book, nil
}

func (store bookStoreInMemory) Create(item book.Book) (uint64, error) {
    store.mu.Lock()
    defer store.mu.Unlock()

    keyInstance = keyInstance + 1
    books[keyInstance] = item

    return keyInstance, nil
}

func (store bookStoreInMemory) Update(id uint64, item book.Book) (*book.Book, error) {
    store.mu.Lock()
    defer store.mu.Unlock()

    books[keyInstance] = item

    return &item, nil
}

func (store bookStoreInMemory) Delete(id uint64) error {
    delete(books, id)
    return nil
}

Enter fullscreen mode Exit fullscreen mode

to represent an in-memory database we will use a map to store our book data. You will see the mu sync.Mutex, we use this dude to create a "safe zone" in the places that we need to increase the number that will represent our Id Generator. If we don't use this lock in these places, a scenario with multiple request (concurrency) trying to add in the map, using the keyInstance probably will be overridden in the map. To create a "safe zone" at the moment that we store the keyInstance, we will use the lock and unlock method to guarantee our process to simulate the id generation.

Now we will access this repository in the service layer, for this reason, we will inject the repository interface into our service constructor.

//internal\service\book.go
//...

type bookServiceImpl struct {
    bookStore repository.BookStore
}

func NewBookService(bookStore repository.BookStore) BookService {
    return bookServiceImpl{bookStore}
}

//...
Enter fullscreen mode Exit fullscreen mode

When you put this interface in the NewBookService() constructor you will see that your 'container.go' will break, because we describe that we need the repository interface in the constructor method and there is the place that we inject/instantiate our components. Then we will change the 'container.go' too.

//internal\container\container.go
//...
func Inject() Container {

    //stores
    bookStore := repository.NewBookStoreInMemory()

    //init services
    bs := service.NewBookService(bookStore)

    //...
}
//...
Enter fullscreen mode Exit fullscreen mode

Now we can access the repository in the service layer because the container will inject the repository correctly. And now we will change the service method to use the repository correctly.

Now our service/book.go will look like this:

package service

import (
    "github.com/maaarkin/hero-api-golang/internal/domain/book"
    "github.com/maaarkin/hero-api-golang/internal/repository"
)

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 {
    bookStore repository.BookStore
}

func NewBookService(bookStore repository.BookStore) BookService {
    return bookServiceImpl{bookStore}
}

func (bs bookServiceImpl) Save(book book.Book) (*book.Book, error) {
    id, err := bs.bookStore.Create(book)

    if err != nil {
        return nil, err
    }

    book.Id = id
    return &book, nil
}

func (bs bookServiceImpl) FindAll() (*[]book.Book, error) {
    return bs.bookStore.GetAll()
}

func (bs bookServiceImpl) FindById(id uint64) (*book.Book, error) {
    return bs.bookStore.Get(id)
}

func (bs bookServiceImpl) Delete(id uint64) error {
    return bs.bookStore.Delete(id)
}

func (bs bookServiceImpl) Update(book book.Book) error {
    _, err := bs.bookStore.Update(book.Id, book)

    if err != nil {
        return err
    }

    return nil
}
Enter fullscreen mode Exit fullscreen mode

At this point, we can access the repository layer through the service layer but we don't have any REST endpoint to get any information from this service. In the next step we will improve our handler/book.go to get information from the service layer and delivery the JSON data.

Discussion (0)