diff --git a/studio-backend/guidelines-repository.md b/studio-backend/guidelines-repository.md index 6c18109..9b8ac0f 100644 --- a/studio-backend/guidelines-repository.md +++ b/studio-backend/guidelines-repository.md @@ -1,276 +1,52 @@ -# Repository Pattern Guidelines for New Developers +# Guidelines on Repository -## Overview - -This guide explains how to properly define repositories in our codebase using the MetaORM framework. The repository pattern provides a clean separation between data access logic and business logic. - -## Repository Structure - -### 1. Repository Interface - -Define a repository interface that extends the base MetaORM repository: +## Basic structure of repository for entity with workspace ```go -type EntityNameRepository interface { - metaorm.Repository[EntityName] - - // Custom query methods with descriptive names - FindByEmail(email string) (*EntityName, error) - FindByUserIdAndWorkspaceId(userId uint, workspaceId uint) (*EntityName, error) - FilteredPagedFindMany( - page metaorm.PaginationQuery, - sort metaorm.SortingQuery, - keywordFilter metaorm.Query, - filterParam uint, - ) ([]EntityName, *metaorm.Pagination, error) +import "git.metadiv.io/metadiv-studio/metaorm" + +func NewWorkspaceMemberLimitRepository(db metaorm.Database, workspaceId uint) WorkspaceMemberLimitRepository { + return &WorkspaceMemberLimitRepositoryImpl{ + WorkspaceRepositoryImpl: metaorm.NewWorkspaceRepositoryImpl[WorkspaceMemberLimit](db, workspaceId), + } +} + +type WorkspaceMemberLimitRepository interface { + metaorm.WorkspaceRepository[WorkspaceMemberLimit] +} + +type WorkspaceMemberLimitRepositoryImpl struct { + metaorm.WorkspaceRepositoryImpl[WorkspaceMemberLimit] } ``` -### 2. Repository Implementation +- Must having NewXXXXRepository method. +- Must having XXXXRepository interface. +- Must having XXXXRepositoryImpl struct. +- The XXXX is entity object. -Implement the repository with proper query building: +## Basic structure of repository for entity without workspace ```go -type EntityNameRepositoryImpl struct { - metaorm.RepositoryImpl[EntityName] -} -``` +import "git.metadiv.io/metadiv-studio/metaorm" -### 3. Constructor Function - -Always provide a constructor function: - -```go -func NewEntityNameRepository(db metaorm.Database) EntityNameRepository { - return &EntityNameRepositoryImpl{ - RepositoryImpl: metaorm.NewRepositoryImpl[EntityName](db), - } -} -``` - -## Query Building Patterns - -### 1. Simple Field Queries - -```go -func (r *RepositoryImpl) FindByEmail(email string) (*Entity, error) { - qb := metaorm.NewQueryBuilder() - return r.Query(qb.Field("email").Eq(email)).FindOne() -} - -func (r *RepositoryImpl) FindById(id uint) (*Entity, error) { - qb := metaorm.NewQueryBuilder() - return r.Query(qb.Field("id").Eq(id)).FindOne() -} -``` - -### 2. Multiple Field Queries - -```go -func (r *RepositoryImpl) FindByUserIdAndWorkspaceId(userId uint, workspaceId uint) (*Entity, error) { - qb := metaorm.NewQueryBuilder() - return r. - Query(qb.And( - qb.Field("user_id").Eq(userId), - qb.Field("workspace_id").Eq(workspaceId), - )). - Preload("Workspace"). - Preload("User"). - FindOne() -} -``` - -### 3. Advanced Filtered Queries (Updated Pattern) - -Use generic interfaces for better flexibility and cleaner code: - -```go -func (r *RepositoryImpl) FilteredPagedFindMany( - page metaorm.PaginationQuery, - sort metaorm.SortingQuery, - keywordFilter metaorm.Query, - workspaceId uint, -) ([]Entity, *metaorm.Pagination, error) { - qb := metaorm.NewQueryBuilder() - queries := make([]metaorm.Query, 0) - - // Base filter - queries = append(queries, qb.Field("workspace_id").Eq(workspaceId)) - - // Add optional keyword filter - if keywordFilter != nil { - queries = append(queries, keywordFilter) - } - - return r. - Query(qb.And(queries...)). - Joins("User"). - Preload("Workspace"). - Pagination(page). - Sorting(sort). - PagedFindMany() -} -``` - -**Key improvements in this pattern:** -- Use `metaorm.PaginationQuery` and `metaorm.SortingQuery` interfaces instead of concrete types -- Accept `metaorm.Query` for keyword filters instead of string + manual query building -- Let the caller build the keyword filter query for better separation of concerns -- More flexible and reusable approach - -### 4. Special Query Methods - -```go -// Unscoped queries (include soft-deleted records) -func (r *RepositoryImpl) UnscopedFindByUserEmail(email string, workspaceId uint) (*Entity, error) { - qb := metaorm.NewQueryBuilder() - return r. - Query(qb.And( - qb.Field("User.email").Eq(email), - qb.Field("workspace_members.workspace_id").Eq(workspaceId), - )). - Joins("User"). - Preload("Workspace"). - Unscoped(). - FindOne() -} - -// Find with exclusions -func (r *RepositoryImpl) FindAnOtherOwner(workspaceMember *Entity) (*Entity, error) { - qb := metaorm.NewQueryBuilder() - return r. - Query(qb.And( - qb.Field("role").Eq(RoleOwner), - qb.Field("workspace_id").Eq(workspaceMember.WorkspaceId.Get()), - qb.Field("id").Neq(workspaceMember.ID), - )). - FindOne() -} - -// Bulk operations -func (r *RepositoryImpl) DeleteAllMembersOfWorkspace(workspaceId uint) error { - qb := metaorm.NewQueryBuilder() - return r.QueriedDelete(qb.Field("workspace_id").Eq(workspaceId)) -} -``` - -## Query Building Best Practices - -### 1. Query Builder Usage - -```go -// Always start with NewQueryBuilder -qb := metaorm.NewQueryBuilder() - -// Simple conditions -qb.Field("field_name").Eq(value) -qb.Field("field_name").Neq(value) -qb.Field("field_name").Similar(keyword) // For LIKE queries - -// Complex conditions -qb.And( - qb.Field("field1").Eq(value1), - qb.Field("field2").Eq(value2), -) - -qb.Or( - qb.Field("field1").Eq(value1), - qb.Field("field2").Eq(value2), -) -``` - -### 2. Relationship Handling - -```go -// For joins and preloading -return r. - Query(qb.Field("id").Eq(id)). - Joins("RelatedEntity"). // Join for filtering - Preload("RelatedEntity"). // Preload for eager loading - FindOne() -``` - -### 3. Pagination and Sorting - -```go -return r. - Query(qb.And(queries...)). - Joins("User"). - Preload("Workspace"). - Pagination(page). - Sorting(sort). - PagedFindMany() -``` - -## Method Naming Conventions - -- `FindBy[Field]` - Find single record by field -- `FindBy[Field1]And[Field2]` - Find by multiple fields -- `FindManyBy[Field]` - Find multiple records -- `FilteredPagedFindMany` - Find with pagination and filtering -- `UnscopedFindBy[Field]` - Find including soft-deleted records -- `DeleteBy[Condition]` - Delete records by condition -- `FindAn[Description]` - Find with specific business logic - -## Key Implementation Rules - -1. **Use generic interfaces** - Prefer `metaorm.PaginationQuery`, `metaorm.SortingQuery`, and `metaorm.Query` over concrete types -2. **Accept query objects** - Let callers build complex filters and pass them as `metaorm.Query` -3. **Always use QueryBuilder** - Never write raw SQL -4. **Use proper field names** - Match database column names exactly -5. **Handle relationships** - Use Joins for filtering, Preload for data -6. **Include comprehensive comments** - Document complex business logic -7. **Follow naming conventions** - Consistent method naming -8. **Return proper types** - Use entity types, not DTOs in repositories -9. **Handle errors properly** - Let MetaORM errors bubble up - -## Example: Complete Repository - -```go func NewUserRepository(db metaorm.Database) UserRepository { - return &UserRepositoryImpl{ - RepositoryImpl: metaorm.NewRepositoryImpl[User](db), - } + return &UserRepositoryImpl{ + RepositoryImpl: metaorm.NewRepositoryImpl[User](db), + } } type UserRepository interface { - metaorm.Repository[User] - FindByEmail(email string) (*User, error) - FindByUsername(username string) (*User, error) - FilteredPagedFindMany( - page metaorm.PaginationQuery, - sort metaorm.SortingQuery, - keywordFilter metaorm.Query, - active bool, - ) ([]User, *metaorm.Pagination, error) + metaorm.Repository[User] } type UserRepositoryImpl struct { - metaorm.RepositoryImpl[User] + metaorm.RepositoryImpl[User] } -func (r *UserRepositoryImpl) FindByEmail(email string) (*User, error) { - qb := metaorm.NewQueryBuilder() - return r.Query(qb.Field("email").Eq(email)).FindOne() -} +``` -func (r *UserRepositoryImpl) FilteredPagedFindMany( - page metaorm.PaginationQuery, - sort metaorm.SortingQuery, - keywordFilter metaorm.Query, - active bool, -) ([]User, *metaorm.Pagination, error) { - qb := metaorm.NewQueryBuilder() - queries := make([]metaorm.Query, 0) - - queries = append(queries, qb.Field("active").Eq(active)) - if keywordFilter != nil { - queries = append(queries, keywordFilter) - } - - return r. - Query(qb.And(queries...)). - Pagination(page). - Sorting(sort). - PagedFindMany() -} +- Must having NewXXXXRepository method. +- Must having XXXXRepository interface. +- Must having XXXXRepositoryImpl struct. +- The XXXX is entity object.