// file: repositories/movie_repository.go package repositories import ( "errors" "sync" "github.com/kataras/iris/v12/_examples/hero/overview/datamodels" ) // Query represents the visitor and action queries. type Query func(datamodels.Movie) bool // MovieRepository handles the basic operations of a movie entity/model. // It's an interface in order to be testable, i.e a memory movie repository or // a connected to an sql database. type MovieRepository interface { Exec(query Query, action Query, limit int, mode int) (ok bool) Select(query Query) (movie datamodels.Movie, found bool) SelectMany(query Query, limit int) (results []datamodels.Movie) InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error) Delete(query Query, limit int) (deleted bool) } // NewMovieRepository returns a new movie memory-based repository, // the one and only repository type in our example. func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository { return &movieMemoryRepository{source: source} } // movieMemoryRepository is a "MovieRepository" // which manages the movies using the memory data source (map). type movieMemoryRepository struct { source map[int64]datamodels.Movie mu sync.RWMutex } const ( // ReadOnlyMode will RLock(read) the data . ReadOnlyMode = iota // ReadWriteMode will Lock(read/write) the data. ReadWriteMode ) func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) { loops := 0 if mode == ReadOnlyMode { r.mu.RLock() defer r.mu.RUnlock() } else { r.mu.Lock() defer r.mu.Unlock() } for _, movie := range r.source { ok = query(movie) if ok { if action(movie) { loops++ if actionLimit >= loops { break // break } } } } return } // Select 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 "found" 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 (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) { found = r.Exec(query, func(m datamodels.Movie) bool { movie = m return true }, 1, ReadOnlyMode) // set an empty datamodels.Movie if not found at all. if !found { movie = datamodels.Movie{} } return } // SelectMany same as Select but returns one or more datamodels.Movie as a slice. // If limit <=0 then it returns everything. func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) { r.Exec(query, func(m datamodels.Movie) bool { results = append(results, m) return true }, limit, ReadOnlyMode) return } // InsertOrUpdate adds or updates a movie to the (memory) storage. // // Returns the new movie and an error if any. func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.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. r.mu.RLock() for _, item := range r.source { if item.ID > lastID { lastID = item.ID } } r.mu.RUnlock() id = lastID + 1 movie.ID = id // map-specific thing r.mu.Lock() r.source[id] = movie r.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: // r.source[id] = movie // and comment the code below; current, exists := r.Select(func(m datamodels.Movie) bool { return m.ID == id }) if !exists { // ID is not a real one, return an error. return datamodels.Movie{}, errors.New("failed to update a nonexistent movie") } // or comment these and r.source[id] = m for pure replace if movie.Poster != "" { current.Poster = movie.Poster } if movie.Genre != "" { current.Genre = movie.Genre } // map-specific thing r.mu.Lock() r.source[id] = current r.mu.Unlock() return movie, nil } func (r *movieMemoryRepository) Delete(query Query, limit int) bool { return r.Exec(query, func(m datamodels.Movie) bool { delete(r.source, m.ID) return true }, limit, ReadWriteMode) }