Skip to content

Instantly share code, notes, and snippets.

@potat-dev
Created July 5, 2024 17:15
Show Gist options
  • Save potat-dev/dcc73fc18668b8dbcb907bea6092ed2f to your computer and use it in GitHub Desktop.
Save potat-dev/dcc73fc18668b8dbcb907bea6092ed2f to your computer and use it in GitHub Desktop.

Repository Pattern Best Practices

Separation of Concerns

Each part of your application should have a well-defined responsibility:

  • Handlers:

    • Focus: Handling HTTP requests, routing, input validation (basic format checks), and output formatting (JSON responses).
    • Minimal Logic: Keep business logic and data access logic out of handlers. They should act as thin orchestrators.
    • Authentication & Authorization (Initial): Check if the user is authenticated (using middleware like your AuthMiddleware). You can also perform initial authorization checks based on readily available information (e.g., is the user trying to access their own profile?).
  • Services:

    • Focus: Encapsulating core business logic and workflows. This includes complex validation rules, data transformations, authorization logic (based on roles, permissions, object ownership, etc.), and interactions with multiple repositories.
    • Domain Experts: Services are the "experts" in your domain (books, users, etc.) and should contain the rules that govern how these entities interact.
  • Repositories:

    • Focus: Data access. Repositories abstract away the database interactions. They provide methods for CRUD operations (Create, Read, Update, Delete) and other data retrieval logic.
    • No Business Logic: Repositories should not contain business rules. They are purely responsible for communicating with the database.

Where to Check User Permissions:

  1. Handlers:

    • Initial authentication (is the user logged in?).
    • Simple authorization based on readily available data (e.g., is the user trying to access their own profile?).
  2. Services:

    • Complex Authorization: Checking if the user has the required permissions to perform an action on a specific book. This often involves:
      • Retrieving the book from the repository.
      • Retrieving the bookshelf from the repository (to check ownership).
      • Retrieving user data (roles, permissions) if necessary.
      • Evaluating if the user's permissions and the book's context allow the action.

Example:

// In BookService.Update:
func (s *BookService) Update(ctx context.Context, bookID string, update *models.BookUpdate) error {
    // 1. Get the book
    book, err := s.repo.GetByID(ctx, bookID)
    if err != nil {
        return fmt.Errorf("failed to get book: %w", err)
    }

    // 2. Get the bookshelf
    bookshelf, err := s.bookshelfRepo.GetByID(ctx, book.BookshelfID) 
    if err != nil {
        return fmt.Errorf("failed to get bookshelf: %w", err)
    }

    // 3. Get userID from context
    userID, ok := ctx.Value("userID").(string)
    if !ok {
        return fmt.Errorf("userID not found in context") 
    }

    // 4. Check bookshelf ownership
    if bookshelf.UserID != userID {
        return fmt.Errorf("user is not authorized to modify this book") 
    }

    // 5. If authorized, proceed with the update:
    if err := s.repo.Update(ctx, bookID, update); err != nil {
        return fmt.Errorf("failed to update book: %w", err)
    }

    return nil
}

Why Minimal Code in Handlers?

  • Testability: Keeping handlers thin makes them easier to test. You can focus on testing request handling and response formatting.
  • Maintainability: If business logic changes, you only need to update the service, not the handler.
  • Code Clarity: It's easier to understand the flow of your application when concerns are separated.

Some Wisdom from Expert:

  • Start Simple: Begin with basic separation. As your application grows, you can refine the layers further.
  • Don't Over-Engineer: Avoid adding unnecessary layers of abstraction if they're not needed.
  • Testing is Crucial: Write tests for all your layers (handlers, services, repositories).

Generated by Gemini 1.5 Pro

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment