iris/_examples/mvc/using-method-result/services/movie_service.go
Gerasimos (Makis) Maropoulos 82b5a1d4ed Enhance the MVC "using-method-result" example. Prev: update to version 8.5.0
Prev Commit: 49ee8f2d75 [formerly 6a3579f2500fc715d7dc606478960946dcade61d]

Changelog: https://github.com/kataras/iris/blob/master/HISTORY.md#mo-09-october-2017--v850

This example is updated with the current commit: https://github.com/kataras/iris/tree/master/_examples/mvc/using-output-result


Former-commit-id: 29486ef014b3667fa1c7c66e11c8e95c76a37e57
2017-10-10 04:46:32 +03:00

207 lines
5.3 KiB
Go

// file: services/movie_service.go
package services
import (
"errors"
"sync"
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
)
// MovieService handles CRUID operations of a movie entity/model.
// It's here to decouple the data source from the higher level compoments.
// As a result a different service for a specific datasource (or repository)
// can be used from the main application without any additional changes.
type MovieService interface {
GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool)
GetByID(id int64) (models.Movie, bool)
InsertOrUpdate(movie models.Movie) (models.Movie, error)
DeleteByID(id int64) bool
GetMany(query func(models.Movie) bool, limit int) (result []models.Movie)
GetAll() []models.Movie
}
// NewMovieServiceFromMemory returns a new memory-based movie service.
func NewMovieServiceFromMemory(source map[int64]models.Movie) MovieService {
return &MovieMemoryService{
source: source,
}
}
// A Movie Service can have different data sources:
// func NewMovieServiceFromDB(db datasource.MySQL) {
// return &MovieDatabaseService{
// db: db,
// }
// }
// Another pattern is to initialize the database connection
// or any source here based on a "string" name or an "enum".
// func NewMovieService(source string) MovieService {
// if source == "memory" {
// return NewMovieServiceFromMemory(datasource.Movies)
// }
// if source == "database" {
// db = datasource.NewDB("....")
// return NewMovieServiceFromDB(db)
// }
// [...]
// return nil
// }
// MovieMemoryService is a "MovieService"
// which manages the movies using the memory data source (map).
type MovieMemoryService struct {
source map[int64]models.Movie
mu sync.RWMutex
}
// GetSingle receives a query function
// which is fired for every single movie model inside
// our imaginary data source.
// When that function returns true then it stops the iteration.
//
// It returns the query's return last known boolean value
// and the last known movie model
// to help callers to reduce the LOC.
//
// It's actually a simple but very clever prototype function
// I'm using everywhere since I firstly think of it,
// hope you'll find it very useful as well.
func (s *MovieMemoryService) GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool) {
s.mu.RLock()
for _, movie = range s.source {
found = query(movie)
if found {
break
}
}
s.mu.RUnlock()
// set an empty models.Movie if not found at all.
if !found {
movie = models.Movie{}
}
return
}
// GetByID returns a movie based on its id.
// Returns true if found, otherwise false, the bool should be always checked
// because the models.Movie may be filled with the latest element
// but not the correct one, although it can be used for debugging.
func (s *MovieMemoryService) GetByID(id int64) (models.Movie, bool) {
return s.GetSingle(func(m models.Movie) bool {
return m.ID == id
})
}
// InsertOrUpdate adds or updates a movie to the (memory) storage.
//
// Returns the new movie and an error if any.
func (s *MovieMemoryService) InsertOrUpdate(movie models.Movie) (models.Movie, error) {
id := movie.ID
if id == 0 { // Create new action
var lastID int64
// find the biggest ID in order to not have duplications
// in productions apps you can use a third-party
// library to generate a UUID as string.
s.mu.RLock()
for _, item := range s.source {
if item.ID > lastID {
lastID = item.ID
}
}
s.mu.RUnlock()
id = lastID + 1
movie.ID = id
// map-specific thing
s.mu.Lock()
s.source[id] = movie
s.mu.Unlock()
return movie, nil
}
// Update action based on the movie.ID,
// here we will allow updating the poster and genre if not empty.
// Alternatively we could do pure replace instead:
// s.source[id] = movie
// and comment the code below;
current, exists := s.GetByID(id)
if !exists { // ID is not a real one, return an error.
return models.Movie{}, errors.New("failed to update a nonexistent movie")
}
// or comment these and s.source[id] = m for pure replace
if movie.Poster != "" {
current.Poster = movie.Poster
}
if movie.Genre != "" {
current.Genre = movie.Genre
}
// map-specific thing
s.mu.Lock()
s.source[id] = current
s.mu.Unlock()
return movie, nil
}
// DeleteByID deletes a movie by its id.
//
// Returns true if deleted otherwise false.
func (s *MovieMemoryService) DeleteByID(id int64) bool {
if _, exists := s.GetByID(id); !exists {
// we could do _, exists := s.source[id] instead
// but we don't because you should learn
// how you can use that service's functions
// with any other source, i.e database.
return false
}
// map-specific thing
s.mu.Lock()
delete(s.source, id)
s.mu.Unlock()
return true
}
// GetMany same as GetSingle but returns one or more models.Movie as a slice.
// If limit <=0 then it returns everything.
func (s *MovieMemoryService) GetMany(query func(models.Movie) bool, limit int) (result []models.Movie) {
loops := 0
s.mu.RLock()
for _, movie := range s.source {
loops++
passed := query(movie)
if passed {
result = append(result, movie)
}
// we have to return at least one movie if "passed" was true.
if limit >= loops {
break
}
}
s.mu.RUnlock()
return
}
// GetAll returns all movies.
func (s *MovieMemoryService) GetAll() []models.Movie {
movies := s.GetMany(func(m models.Movie) bool { return true }, -1)
return movies
}