Skip to main content

Overview

Repository interfaces define the contract for data persistence operations. Concrete implementations can use CSV, PostgreSQL, or other storage mechanisms.

MovieRepository

Interface for movie persistence operations.

Class Definition

from abc import ABC, abstractmethod
from typing import Optional
from domain.models.movie import Movie

class MovieRepository(ABC):
    @abstractmethod
    def save(self, movie: Movie) -> Movie:
        pass
    
    @abstractmethod
    def find_by_imdb_id(self, imdb_id: str) -> Optional[Movie]:
        pass
Source: domain/repositories/movie_repository.py:5-38

Methods

save

Persists a movie and returns the saved entity with any generated fields.
def save(self, movie: Movie) -> Movie
movie
Movie
required
Movie object containing the information to save.
return
Movie
The saved Movie entity, including any database-generated fields (e.g., id).
Source: domain/repositories/movie_repository.py:13-25

find_by_imdb_id

Searches for a movie by its IMDb ID to prevent duplicates.
def find_by_imdb_id(self, imdb_id: str) -> Optional[Movie]
imdb_id
str
required
IMDb unique identifier (e.g., tt0111161).
return
Optional[Movie]
The Movie object if found, otherwise None.
Source: domain/repositories/movie_repository.py:27-38

Implementations


ActorRepository

Interface for actor persistence operations.

Class Definition

from abc import ABC, abstractmethod
from typing import Optional
from domain.models.actor import Actor

class ActorRepository(ABC):
    @abstractmethod
    def save(self, actor: Actor) -> Actor:
        pass
    
    @abstractmethod
    def find_by_name(self, name: str) -> Optional[Actor]:
        pass
Source: domain/repositories/actor_repository.py:5-28

Methods

save

Persists an actor and returns the saved entity.
def save(self, actor: Actor) -> Actor
actor
Actor
required
Actor object to save.
return
Actor
The saved Actor entity with database-generated ID.
Source: domain/repositories/actor_repository.py:10-15

find_by_name

Searches for an actor by name to prevent duplicates.
def find_by_name(self, name: str) -> Optional[Actor]
name
str
required
Actor’s full name to search for.
return
Optional[Actor]
The Actor object if found, otherwise None.
Source: domain/repositories/actor_repository.py:17-28

Implementations


MovieActorRepository

Interface for managing movie-actor relationships (N:M).

Class Definition

from abc import ABC, abstractmethod
from typing import List
from domain.models.movie_actor import MovieActor

class MovieActorRepository(ABC):
    @abstractmethod
    def save(self, relation: MovieActor) -> None:
        pass
    
    @abstractmethod
    def save_many(self, relations: List[MovieActor]) -> None:
        pass
Source: domain/repositories/movie_actor_repository.py:5-33

Methods

save

Persists a single movie-actor relationship.
def save(self, relation: MovieActor) -> None
relation
MovieActor
required
The relationship to persist.
Source: domain/repositories/movie_actor_repository.py:10-20
Useful for adding a single association. For bulk operations, use save_many for better performance.

save_many

Persists multiple movie-actor relationships efficiently.
def save_many(self, relations: List[MovieActor]) -> None
relations
List[MovieActor]
required
List of MovieActor relationships to persist.
Source: domain/repositories/movie_actor_repository.py:22-33
Preferred method when processing a movie with multiple actors, as it optimizes write operations.

Implementations


Usage Example

from domain.models.movie import Movie
from domain.models.actor import Actor
from domain.models.movie_actor import MovieActor
from infrastructure.persistence.csv.repositories import (
    MovieCsvRepository,
    ActorCsvRepository,
    MovieActorCsvRepository
)

# Initialize repositories
movie_repo = MovieCsvRepository()
actor_repo = ActorCsvRepository()
relation_repo = MovieActorCsvRepository()

# Save a movie
movie = Movie(
    id=None,
    imdb_id="tt0111161",
    title="The Shawshank Redemption",
    year=1994,
    rating=9.3,
    duration_minutes=142,
    metascore=82,
    actors=[]
)
saved_movie = movie_repo.save(movie)

# Save actors and relationships
for actor_name in ["Tim Robbins", "Morgan Freeman"]:
    # Check if actor exists
    existing_actor = actor_repo.find_by_name(actor_name)
    if existing_actor:
        saved_actor = existing_actor
    else:
        actor = Actor(id=None, name=actor_name)
        saved_actor = actor_repo.save(actor)
    
    # Create relationship
    relation = MovieActor(
        movie_id=saved_movie.id,
        actor_id=saved_actor.id
    )
    relation_repo.save(relation)

Repository Pattern Benefits

Abstraction

Business logic doesn’t depend on storage mechanism:
class SaveMovieUseCase:
    def __init__(self, movie_repository: MovieRepository):
        self.repo = movie_repository  # Works with any implementation

Testing

Easy to create in-memory implementations for tests:
class InMemoryMovieRepository(MovieRepository):
    def __init__(self):
        self.movies = {}
    
    def save(self, movie: Movie) -> Movie:
        movie.id = len(self.movies) + 1
        self.movies[movie.imdb_id] = movie
        return movie
    
    def find_by_imdb_id(self, imdb_id: str) -> Optional[Movie]:
        return self.movies.get(imdb_id)

Flexibility

Swap storage implementations without changing business logic:
# Development: Use CSV
repo = MovieCsvRepository()

# Production: Use PostgreSQL
repo = MoviePostgresRepository(connection)

# Testing: Use in-memory
repo = InMemoryMovieRepository()

Build docs developers (and LLMs) love