Design Patterns Application¶
GoVector is a lightweight embedded vector database written in pure Go, providing a Qdrant-like REST API interface. This project demonstrates practical application of various design patterns including Factory Pattern, Strategy Pattern, Observer Pattern, and Adapter Pattern. The choice of these design patterns not only improves code maintainability and extensibility but also provides flexible solutions for different application scales.
Project Structure¶
GoVector uses a modular project structure with clear separation of different concerns:
graph TB
subgraph "Command Line Entry"
CLI[cmd/govector/main.go]
end
subgraph "API Layer"
APIServer[api/server.go]
end
subgraph "Core Business Logic"
Collection[core/collection.go]
Storage[core/storage.go]
Models[core/models.go]
end
subgraph "Index Engine"
IndexInterface[core/index.go]
FlatIndex[core/flat_index.go]
HNSWIndex[core/hnsw_index.go]
end
CLI --> APIServer
APIServer --> Collection
Collection --> Storage
Collection --> IndexInterface
IndexInterface --> FlatIndex
IndexInterface --> HNSWIndex
Core Components¶
GoVector's core components are built around vector collections (Collection), where each collection represents a logically grouped set of vectors, similar to tables in SQL databases. Collaboration between components achieves high decoupling through interface abstraction.
Architecture Overview¶
GoVector adopts a layered architecture design with clear responsibilities at each layer from bottom storage to top API:
graph TB
subgraph "User Interface Layer"
Client[Client Application]
end
subgraph "API Gateway Layer"
HTTPServer[HTTP Server]
APIMiddleware[API Middleware]
end
subgraph "Business Logic Layer"
CollectionLayer[Collection Management Layer]
FilterEngine[Filter Engine]
end
subgraph "Data Access Layer"
StorageEngine[Storage Engine]
IndexEngine[Index Engine]
end
subgraph "Persistence Layer"
BoltDB[BoltDB Storage]
Protobuf[Protocol Buffers]
end
Client --> HTTPServer
HTTPServer --> APIMiddleware
APIMiddleware --> CollectionLayer
CollectionLayer --> FilterEngine
CollectionLayer --> IndexEngine
IndexEngine --> StorageEngine
StorageEngine --> BoltDB
StorageEngine --> Protobuf
Detailed Component Analysis¶
Factory Pattern: Collection and Index Creation¶
GoVector applies the Factory Pattern in multiple places to create complex object instances, especially Collection and various index types.
Collection Factory Method¶
The Collection class uses the factory method pattern to create different types of index instances:
classDiagram
class Collection {
+string Name
+int VectorLen
+Distance Metric
+VectorIndex index
+Storage storage
+NewCollection(name, vectorLen, metric, store, useHNSW)
+NewCollectionWithParams(name, vectorLen, metric, store, useHNSW, params)
+Upsert(points)
+Search(query, filter, topK)
+Delete(points, filter)
}
class VectorIndex {
<>
+Upsert(points)
+Search(query, filter, topK)
+Delete(id)
+Count()
+GetIDsByFilter(filter)
+DeleteByFilter(filter)
}
class FlatIndex {
+map[string]*PointStruct points
+Distance metric
+NewFlatIndex(metric)
+Upsert(points)
+Search(query, filter, topK)
+Delete(id)
+Count()
+GetIDsByFilter(filter)
+DeleteByFilter(filter)
}
class HNSWIndex {
+Graph graph
+map[string]*PointStruct points
+Distance metric
+HNSWParams params
+NewHNSWIndex(metric)
+NewHNSWIndexWithParams(metric, params)
+Upsert(points)
+Search(query, filter, topK)
+Delete(id)
+Count()
+GetIDsByFilter(filter)
+DeleteByFilter(filter)
}
Collection --> VectorIndex : "creates"
VectorIndex <|.. FlatIndex : "implements"
VectorIndex <|.. HNSWIndex : "implements"
Index Factory Implementation¶
The core of the factory pattern lies in dynamically selecting the appropriate index implementation based on configuration:
flowchart TD
Start([Create Collection]) --> CheckHNSW{"Use HNSW?"}
CheckHNSW --> |Yes| CreateHNSW["Create HNSWIndex instance"]
CheckHNSW --> |No| CreateFlat["Create FlatIndex instance"]
CreateHNSW --> SetIndex["Set Collection.index"]
CreateFlat --> SetIndex
SetIndex --> InitStorage["Initialize storage engine"]
InitStorage --> LoadPoints["Load existing data"]
LoadPoints --> End([Complete])
Strategy Pattern: Multiple Implementations of VectorIndex Interface¶
The VectorIndex interface defines a unified strategy interface, allowing FlatIndex and HNSWIndex to be transparently used as different search strategies.
Strategy Interface Design¶
classDiagram
class VectorIndex {
<>
+Upsert(points) error
+Search(query, filter, topK) []ScoredPoint
+Delete(id) error
+Count() int
+GetIDsByFilter(filter) []string
+DeleteByFilter(filter) ([]string, error)
}
class FlatIndexStrategy {
+Distance metric
+map[string]*PointStruct points
+Search(query, filter, topK) []ScoredPoint
+Count() int
}
class HNSWStrategy {
+Graph graph
+Distance metric
+HNSWParams params
+Search(query, filter, topK) []ScoredPoint
+Count() int
}
VectorIndex <|.. FlatIndexStrategy
VectorIndex <|.. HNSWStrategy
Strategy Switching Mechanism¶
The strategy pattern allows switching between different search algorithms at runtime based on requirements:
Observer Pattern: Graceful Shutdown and Signal Handling¶
GoVector uses the observer pattern to handle system signals, implementing graceful shutdown flow.
Signal Handling Flow¶
sequenceDiagram
participant Main as Main Program
participant Signal as OS Signal
participant Server as HTTP Server
participant Storage as Storage Engine
Main->>Signal : Register signal handler
Note over Main : Start service listening
Signal->>Main : Send SIGTERM/SIGINT
Main->>Main : Create timeout context
Main->>Server : Call Stop(ctx)
Server->>Server : Shutdown(ctx)
Note over Server : Stop accepting new connections
Server->>Storage : Close storage connection
Storage-->>Server : Confirm close
Server-->>Main : Return shutdown result
Main-->>Main : Output shutdown log
Graceful Shutdown Implementation¶
The observer pattern's concrete implementation in GoVector includes:
- Signal Registration: Use
signal.Notifyto register OS signals - Asynchronous Handling: Handle server startup through goroutine
- Timeout Control: Use
context.WithTimeoutto control shutdown time - Resource Cleanup: Ensure all resources are released in correct order
Adapter Pattern: API Layer and Business Logic Layer Adaptation¶
The adapter pattern is used for decoupling between API layer and business logic layer, converting HTTP requests into parameter formats required by business logic.
API Adapter Flow¶
flowchart TD
HTTPReq[HTTP Request] --> JSONParse[JSON Parse]
JSONParse --> ParamValidation[Parameter Validation]
ParamValidation --> Adapter[Parameter Adapter]
Adapter --> BusinessLogic[Business Logic Call]
BusinessLogic --> ResultAdapter[Result Adapter]
ResultAdapter --> JSONResponse[JSON Response]
subgraph "FlatIndex Adapter"
FlatAdapter[FlatIndex Adapter]
end
subgraph "HNSWIndex Adapter"
HNSWAdapter[HNSWIndex Adapter]
end
BusinessLogic --> FlatAdapter
BusinessLogic --> HNSWAdapter
Concrete Adapter Implementation¶
The API layer adapts business logic through:
- Request Parsing: Parse HTTP request body into Go structs
- Parameter Conversion: Convert string parameters to internal enum values
- Error Handling: Convert business logic errors to HTTP status codes
- Response Wrapping: Wrap business results into standard JSON format
Template Method Pattern: Collection Operation Flow¶
The Collection class uses the template method pattern to define standard flows for Upsert and Delete operations, while allowing subclasses to override specific steps.
Template Method Flow¶
flowchart TD
TemplateMethod[Template Method] --> Validate[Validate input]
Validate --> PersistFirst[Persist to disk]
PersistFirst --> UpdateIndex[Update in-memory index]
UpdateIndex --> Success[Return success]
subgraph "Upsert Flow"
UpsertTemplate[Upsert Template]
UpsertTemplate --> Validate
UpsertTemplate --> PersistFirst
UpsertTemplate --> UpdateIndex
end
subgraph "Delete Flow"
DeleteTemplate[Delete Template]
DeleteTemplate --> Validate
DeleteTemplate --> PersistFirst
DeleteTemplate --> UpdateIndex
end
Dependency Analysis¶
GoVector's dependency relationships reflect clear layered architecture and loose coupling design:
graph TB
subgraph "External Dependencies"
BBolt[go.etcd.io/bbolt]
HNSW[github.com/coder/hnsw]
Protobuf[google.golang.org/protobuf]
end
subgraph "Internal Modules"
Core[core package]
API[api package]
CMD[cmd package]
end
subgraph "Core Functionality"
Storage[Storage Engine]
Collection[Collection Management]
Index[Index Engine]
Filter[Filter]
end
CMD --> API
API --> Core
Core --> Storage
Core --> Collection
Core --> Index
Core --> Filter
Storage --> BBolt
Index --> HNSW
Storage --> Protobuf
Performance Considerations¶
Performance Impact of Design Patterns¶
- Factory Pattern: Object creation overhead is minimal, main impact is runtime memory allocation
- Strategy Pattern: Interface calls have slight performance overhead, but换来了解耦的优势
- Observer Pattern: Signal handling performance impact is negligible
- Adapter Pattern: JSON serialization/deserialization performance cost is relatively high
Performance Optimization Suggestions¶
- Batch Operations: Use Collection's batch Upsert functionality to reduce storage overhead
- Index Selection: Choose appropriate index type based on data scale
- Memory Management: Properly configure HNSW parameters to balance precision and performance
- Concurrency Control: Use read-write locks to optimize performance in high-concurrency scenarios
Troubleshooting Guide¶
Common Problems and Solutions¶
- Index creation failed: Check if VectorLen is consistent with stored data
- Query performance issues: Consider switching to HNSW index or adjusting HNSW parameters
- Insufficient storage space: Enable vector quantization to reduce storage space
- Graceful shutdown failed: Check if there are long-running operations blocking
Conclusion¶
GoVector project demonstrates best practices of design patterns in real projects. Through organic combination of Factory Pattern, Strategy Pattern, Observer Pattern and Adapter Pattern, the project achieves:
- High extensibility: New index types can be easily added
- Good maintainability: Clear separation of responsibilities between modules
- Excellent performance: Choose optimal implementation strategy according to scenario
- Strong compatibility: Supports multiple usage modes and deployment methods
The choice of these design patterns fully considers the actual needs of the project, maximizing code quality and development efficiency while ensuring functional completeness. For similar embedded vector database projects, GoVector provides an excellent reference example.