# Specification Pattern Implementation The Specification pattern allows you to encapsulate business logic for filtering and querying data. Instead of writing SQL queries directly, you build specifications using Go code that can later be converted to SQL, used for in-memory filtering, or other purposes. ## Core Components ### 1. Basic Data Structures (`condition_spec.go`) #### `Condition` Represents a single comparison operation: ```go type Condition struct { Field string // Field name (e.g., "age", "name") Operator string // Comparison operator (e.g., "=", ">", "IN") Value interface{} // Value to compare against } ``` #### `LogicalGroup` Combines multiple conditions with logical operators: ```go type LogicalGroup struct { Operator string // "AND", "OR", or "NOT" Conditions []Condition // Simple conditions Spec *Spec // Nested specification for complex logic } ``` #### `Spec` The main specification container (can hold either a single condition or a logical group): ```go type Spec struct { Condition *Condition // For simple conditions LogicalGroup *LogicalGroup // For complex logic } ``` ### 2. Builder Functions These functions create specifications in a fluent, readable way: ```go // Simple conditions userSpec := Eq("name", "John") // name = "John" ageSpec := Gt("age", 18) // age > 18 roleSpec := In("role", []interface{}{"admin", "moderator"}) // role IN ("admin", "moderator") // Logical combinations complexSpec := And( Eq("status", "active"), Or( Gt("age", 21), Eq("role", "admin"), ), ) ``` ### 3. Specification Interface (`simple_specification.go`) The `Specification[T]` interface provides methods for: - **Evaluation**: `IsSatisfiedBy(candidate T) bool` - **Composition**: `And()`, `Or()`, `Not()` - **Introspection**: `GetConditions()`, `GetSpec()` ## How It Works ### 1. Building Specifications ```go // Create a specification for active users over 18 spec := And( Eq("status", "active"), Gt("age", 18), ) ``` ### 2. Evaluating Specifications The `SimpleSpecification` uses reflection to evaluate conditions against Go objects: ```go user := &User{Name: "John", Age: 25, Status: "active"} specification := NewSimpleSpecification[*User](spec) isMatch := specification.IsSatisfiedBy(user) // true ``` ### 3. Field Access The implementation looks for field values in this order: 1. `GetFieldName()` method (e.g., `GetAge()`) 2. `FieldName()` method (e.g., `Age()`) 3. Exported struct field ### 4. Type Comparisons The system handles different data types intelligently: - **Numbers**: Direct comparison with type conversion - **Strings**: Lexicographic comparison - **Time**: Special handling for `time.Time` and objects with `Time()` methods - **Collections**: `IN` and `NOT IN` operations - **Nil values**: Proper null handling ## Examples ### Simple Usage ```go // Find all active users activeSpec := Eq("status", "active") spec := NewSimpleSpecification[*User](activeSpec) users := []User{{Status: "active"}, {Status: "inactive"}} for _, user := range users { if spec.IsSatisfiedBy(&user) { fmt.Println("Active user:", user.Name) } } ``` ### Complex Logic ```go // Find admin users OR users with high scores complexSpec := Or( Eq("role", "admin"), And( Gt("score", 90), Eq("status", "active"), ), ) ``` ### Time Comparisons ```go // Find expired items expiredSpec := Lte("expirationDate", time.Now()) ``` ## Key Benefits 1. **Type Safety**: Compile-time checking with generics 2. **Readability**: Business logic expressed in readable Go code 3. **Reusability**: Specifications can be composed and reused 4. **Testability**: Easy to test business rules in isolation 5. **Flexibility**: Can be converted to SQL, used for filtering, etc. ## Performance Considerations - Reflection is used for field access (consider caching for high-frequency operations) - Complex nested specifications may impact performance - Time comparisons handle multiple formats but may be slower than direct comparisons ## Common Patterns ### Repository Integration ```go type UserRepository interface { FindWhere(ctx context.Context, spec Specification[*User]) ([]*User, error) } ``` ### Business Rule Encapsulation ```go func ActiveUserSpec() *Spec { return And( Eq("status", "active"), Neq("deletedAt", nil), ) } ``` ### Dynamic Query Building ```go func BuildUserSearchSpec(filters UserFilters) *Spec { conditions := []*Spec{} if filters.Status != "" { conditions = append(conditions, Eq("status", filters.Status)) } if filters.MinAge > 0 { conditions = append(conditions, Gte("age", filters.MinAge)) } return And(conditions...) }