Update studio-backend/guidelines-repository.md
This commit is contained in:
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user