Repository Pattern Implementation for Blog-Post with photos application

64 Views Asked by At

Consider a application that allows you to create, list and view blog-posts with photos.

However the application also gives a place where user can view all the photos they have added in all of their blog posts. User can also update captions of these photos individually.

Note we do not want the user to be able to add or delete or update a photo on its own. They can only do so in context of a blog.

For this purpose I need a list, update, create blog-post (with photos) API. I also need a seperate list and updateCaption API for the photos.

I am trying to understand how do I fit this in a repository pattern and DDD.
Considering repository is created per domain aggregate, I feel the best way is to have two repositories blogpost and photo. The blogpost repository has methods to update/create/list blog post with photos. And another photo repository which simply has a list and updateCaption method. Is this a correct implementation?

One concern that I have for this implementation is that I have photo as an aggregate root and as a entity in blogpost aggregate and it might not be a correct DDD/repository-pattern implementation.

In pkg repository/blogpost/

// in entity.go file
type BlogPost struct {
    ID      string
    Name    string
    Author  string
    Content string
    Photos  []Photo
}

type Photo struct {
    ID      string
    Caption string
    URL     string
}

// repository.go
type repository interface {
    CreateBlogPost(context.Context, BlogPost) (BlogPost, error)
    UpdateBlogPost(context.Context, BlogPost) (BlogPost, error)
    ListBlogPosts(context.Context) ([]BlogPost, error)
}

In pkg repository/photo/

// in entity.go file
type Photo struct {
    ID         string
    Caption    string
    BlogPostID int
    URL        string
}

// in repository.go file
type repository interface {
    UpdateCaption(ctx context.Context, ID string, Caption string) (Photo, error)
    ListPhotos(context.Context) ([]Photo, error)
}
1

There are 1 best solutions below

2
Navid Zarepak On

There is no point in duplicating the Photo struct/repository. What you have is two different repositories. Photo is linked to a blog post using an ID.

What you can do is handle this relation outside of the repository package. When a create request comes in, create the post in the blogpost repository and then create the photos in the photo repository linked to the Post ID.

To get the photos for a post, create a method that returns photos linked to the given blog post:

photo repository:

type repository interface {
    CreatePhoto(context.Context, Photo) (Photo, error)
    UpdateCaption(ctx context.Context, ID string, Caption string) (Photo, error)
    ListPhotos(context.Context) ([]Photo, error)
    PostPhotos(context.Context, postID string) ([]Photo, error)
}

To create and retrieve posts and photos, consider a package name service as an example:

service:

func CreatePost(post BlogPost, photos []Photo) error {
    p, err := blogpostRepo.CreateBlogPost(context.TODO(), post)
    if err != nil {
        return err
    }
    for _, photo := range photos {
        photo.PostID = p.ID
        _, err := photoRepo.CreatePhoto(context.TODO(), photo)
        if err != nil {
            return err
        }
    }
}

func GetPost(id string) (BlogPost, error) {
    post, err := blogpostRepo.GetPost(id)
    // handle error
    postPhotos, err := photoRepo.PostPhotos(id)
    // handle error
}

You should have different structs in your service layer to avoid sending out repository structs. Basically convert repository structs to service structs and service structs to repository structs.

It's also a good idea to run create methods inside of a transaction. You can use dependency injection to inject the transaction.