|
| 1 | +--- |
| 2 | +description: Code patterns |
| 3 | +globs: |
| 4 | +alwaysApply: false |
| 5 | +--- |
| 6 | +# Go Code Patterns and Conventions |
| 7 | + |
| 8 | +## Architecture Pattern: Repository-Service-Handler |
| 9 | + |
| 10 | +### Repository Layer |
| 11 | +```go |
| 12 | +type Repository interface { |
| 13 | + Create(entity *Entity) error |
| 14 | + GetByID(id uint) (*Entity, error) |
| 15 | + Update(entity *Entity) error |
| 16 | + Delete(id uint) error |
| 17 | +} |
| 18 | + |
| 19 | +type repository struct { |
| 20 | + db *gorm.DB |
| 21 | +} |
| 22 | + |
| 23 | +func NewRepository(db *gorm.DB) Repository { |
| 24 | + return &repository{db: db} |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +### Service Layer |
| 29 | +```go |
| 30 | +type Service interface { |
| 31 | + BusinessOperation(input Input) (*Output, error) |
| 32 | +} |
| 33 | + |
| 34 | +type service struct { |
| 35 | + repo Repository |
| 36 | + // other dependencies |
| 37 | +} |
| 38 | + |
| 39 | +func NewService(repo Repository) Service { |
| 40 | + return &service{repo: repo} |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +### Handler Layer |
| 45 | +```go |
| 46 | +type Handler struct { |
| 47 | + service Service |
| 48 | +} |
| 49 | + |
| 50 | +func NewHandler(service Service) *Handler { |
| 51 | + return &Handler{service: service} |
| 52 | +} |
| 53 | + |
| 54 | +// @Summary Operation description |
| 55 | +// @Router /endpoint [method] |
| 56 | +func (h *Handler) HandlerMethod(c *gin.Context) { |
| 57 | + // Request binding |
| 58 | + // Validation |
| 59 | + // Service call |
| 60 | + // Response |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +## Dependency Injection Pattern |
| 65 | + |
| 66 | +### Constructor Functions |
| 67 | +All components use constructor functions with dependency injection: |
| 68 | +```go |
| 69 | +func NewService(repo Repository, emailService EmailService) Service { |
| 70 | + return &service{ |
| 71 | + repo: repo, |
| 72 | + emailService: emailService, |
| 73 | + } |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +### Main Function Organization |
| 78 | +In [cmd/api/main.go](mdc:cmd/api/main.go): |
| 79 | +1. Initialize infrastructure (DB, Redis) |
| 80 | +2. Create repositories |
| 81 | +3. Create services with dependencies |
| 82 | +4. Create handlers |
| 83 | +5. Setup routes |
| 84 | + |
| 85 | +## Error Handling Patterns |
| 86 | + |
| 87 | +### Custom Error Types |
| 88 | +Define in `pkg/errors/`: |
| 89 | +```go |
| 90 | +type AuthError struct { |
| 91 | + Code string |
| 92 | + Message string |
| 93 | + Err error |
| 94 | +} |
| 95 | + |
| 96 | +func (e *AuthError) Error() string { |
| 97 | + return e.Message |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +### Error Response Pattern |
| 102 | +```go |
| 103 | +if err != nil { |
| 104 | + c.JSON(http.StatusBadRequest, gin.H{ |
| 105 | + "success": false, |
| 106 | + "error": err.Error(), |
| 107 | + }) |
| 108 | + return |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +### Success Response Pattern |
| 113 | +```go |
| 114 | +c.JSON(http.StatusOK, gin.H{ |
| 115 | + "success": true, |
| 116 | + "data": result, |
| 117 | +}) |
| 118 | +``` |
| 119 | + |
| 120 | +## Database Patterns |
| 121 | + |
| 122 | +### GORM Models |
| 123 | +Located in `pkg/models/`: |
| 124 | +```go |
| 125 | +type User struct { |
| 126 | + ID uint `gorm:"primaryKey" json:"id"` |
| 127 | + Email string `gorm:"unique;not null" json:"email"` |
| 128 | + Password string `gorm:"not null" json:"-"` |
| 129 | + CreatedAt time.Time `json:"created_at"` |
| 130 | + UpdatedAt time.Time `json:"updated_at"` |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +### Repository Methods |
| 135 | +```go |
| 136 | +func (r *repository) GetByEmail(email string) (*models.User, error) { |
| 137 | + var user models.User |
| 138 | + if err := r.db.Where("email = ?", email).First(&user).Error; err != nil { |
| 139 | + return nil, err |
| 140 | + } |
| 141 | + return &user, nil |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +## DTO (Data Transfer Object) Patterns |
| 146 | + |
| 147 | +### Request DTOs |
| 148 | +```go |
| 149 | +type LoginRequest struct { |
| 150 | + Email string `json:"email" validate:"required,email" example:"user@example.com"` |
| 151 | + Password string `json:"password" validate:"required,min=8" example:"password123"` |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +### Response DTOs |
| 156 | +```go |
| 157 | +type UserResponse struct { |
| 158 | + ID uint `json:"id" example:"1"` |
| 159 | + Email string `json:"email" example:"user@example.com"` |
| 160 | + CreatedAt string `json:"created_at" example:"2023-01-01T00:00:00Z"` |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +## Swagger Documentation Pattern |
| 165 | + |
| 166 | +### Handler Annotations |
| 167 | +```go |
| 168 | +// @Summary User login |
| 169 | +// @Description Authenticate user with email and password |
| 170 | +// @Tags authentication |
| 171 | +// @Accept json |
| 172 | +// @Produce json |
| 173 | +// @Param request body dto.LoginRequest true "Login credentials" |
| 174 | +// @Success 200 {object} dto.APIResponse{data=dto.LoginResponse} |
| 175 | +// @Failure 400 {object} dto.APIResponse |
| 176 | +// @Router /login [post] |
| 177 | +``` |
| 178 | + |
| 179 | +## Configuration Pattern |
| 180 | + |
| 181 | +### Environment Variables |
| 182 | +Using Viper in [cmd/api/main.go](mdc:cmd/api/main.go): |
| 183 | +```go |
| 184 | +viper.AutomaticEnv() |
| 185 | +viper.SetDefault("PORT", "8080") |
| 186 | +viper.SetDefault("ACCESS_TOKEN_EXPIRATION_MINUTES", 15) |
| 187 | +``` |
| 188 | + |
| 189 | +## Testing Patterns |
| 190 | + |
| 191 | +### Table-Driven Tests |
| 192 | +```go |
| 193 | +func TestService_Method(t *testing.T) { |
| 194 | + tests := []struct { |
| 195 | + name string |
| 196 | + input Input |
| 197 | + want Output |
| 198 | + wantErr bool |
| 199 | + }{ |
| 200 | + // test cases |
| 201 | + } |
| 202 | + |
| 203 | + for _, tt := range tests { |
| 204 | + t.Run(tt.name, func(t *testing.T) { |
| 205 | + // test implementation |
| 206 | + }) |
| 207 | + } |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +### Mock Interfaces |
| 212 | +Use interfaces for all dependencies to enable mocking in tests. |
| 213 | + |
| 214 | +## Security Code Patterns |
| 215 | + |
| 216 | +### Password Hashing |
| 217 | +```go |
| 218 | +func HashPassword(password string) (string, error) { |
| 219 | + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |
| 220 | + return string(bytes), err |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | +### JWT Token Generation |
| 225 | +```go |
| 226 | +func GenerateToken(userID uint, tokenType string) (string, error) { |
| 227 | + claims := jwt.MapClaims{ |
| 228 | + "user_id": userID, |
| 229 | + "type": tokenType, |
| 230 | + "exp": time.Now().Add(expiration).Unix(), |
| 231 | + } |
| 232 | + // token generation logic |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +## Activity Logging Pattern |
| 237 | + |
| 238 | +### Log Service Usage |
| 239 | +```go |
| 240 | +logService.LogActivity(logService.ActivityLogParams{ |
| 241 | + UserID: userID, |
| 242 | + EventType: "login_success", |
| 243 | + Description: "User logged in successfully", |
| 244 | + IPAddress: c.ClientIP(), |
| 245 | + UserAgent: c.GetHeader("User-Agent"), |
| 246 | +}) |
| 247 | +``` |
| 248 | + |
| 249 | +## File Organization Rules |
| 250 | + |
| 251 | +### Package Structure |
| 252 | +- One responsibility per package |
| 253 | +- Internal packages in `internal/` |
| 254 | +- Shared packages in `pkg/` |
| 255 | +- Feature-based organization |
| 256 | + |
| 257 | +### File Naming |
| 258 | +- `handler.go` for HTTP handlers |
| 259 | +- `service.go` for business logic |
| 260 | +- `repository.go` for data access |
| 261 | +- `models.go` for database models |
| 262 | +- `dto.go` for data transfer objects |
| 263 | + |
0 commit comments