- Learning-focused microservices implementation
- Simplicity over complexity
- Clear, educational design patterns
- Demonstrate microservices communication
- Implement event-driven workflows
- Showcase Go programming practices
-
Synchronous (gRPC)
- Critical, immediate interactions
- Presigning upload URLs
- Authentication checks
-
Asynchronous (Message Queues)
- Background processing
- Event-driven workflows
- Decoupled service interactions
- Handles user authentication
- Manages file upload workflows
- Coordinates upload processes
- Processes uploaded files
- Extracts file metadata
- Applies transformations
- Manages file storage
- Handles compression
- Provides file retrieval mechanisms
User Upload Request
↓
FileUploadService (gRPC)
├── Validate Request
├── Generate Upload Token
└── Initiate Storage Process
↓
FileStorageService
├── Compress File
├── Store Metadata
└── Publish Upload Event
↓
Message Queue
└── FileProcessorService
├── Process File
└── Store Results
- gRPC for immediate interactions
- NATS for event-driven communication
- Local filesystem
- SQLite for metadata
- Zstandard compression
- Minimal, learning-focused transformations
- Support for text-based files
- Concurrent upload support
- Minimal processing overhead
- Efficient message routing
- Single-node deployment
- Limited to text file processing
- Minimal error handling
- Distributed architecture
- Advanced processing capabilities
- Enhanced error management
- Strong typing
- Excellent concurrency support
- Compiled performance
- Simple syntax
- Efficient binary communication
- Strong typing
- Built-in streaming
- Language-agnostic
- Decoupled service communication
- Scalable event handling
- Reliable message delivery
- Basic token-based authentication
- Secure file upload mechanisms
- Minimal attack surface
- Learning-focused security model
- Basic logging
- Performance metrics
- Simple error tracking
Our microservices architecture employs a hybrid communication approach:
- Synchronous Communication: gRPC
- Asynchronous Communication: NATS Message Queue
-
Synchronous gRPC
- Critical, immediate interactions
- Real-time request/response
- Strong typing via Protocol Buffers
- Used for:
- Authentication
- Upload URL generation
- Metadata retrieval
-
Asynchronous NATS
- Background processing
- Decoupled service interactions
- Event-driven workflows
- Used for:
- File processing triggers
- Notification broadcasts
- Asynchronous task management
Client Request
│
├── API Service (gRPC)
│ ├── Authentication
│ ├── Request Validation
│ └── Immediate Responses
│
└── Background Processing
└── NATS Message Queue
├── File Processing Events
├── Service Notifications
└── Asynchronous Workflows
- Lightweight message broker
- High-performance pub/sub system
- Simple, intuitive API
- Native Go support
- JetStream for persistent messaging
-
Topics
file.upload.initiatedfile.process.requestfile.process.completedservice.notification
-
Queue Groups
- Load balanced message consumption
- Guaranteed single processing of messages
// Publishing a file processing event
nc.Publish("file.process.request", &FileProcessEvent{
FileID: "unique-file-id",
Metadata: fileMetadata,
})
// Subscribing to processing events
nc.Subscribe("file.process.request", func(msg *nats.Msg) {
// Process file asynchronously
})-
Flexibility
- Choose right communication method per use case
- Optimize for performance and complexity
-
Scalability
- Decoupled service interactions
- Independent service scaling
- Resilient to temporary service unavailability
-
Performance
- Low-latency gRPC for critical paths
- Efficient message routing with NATS
- Minimal overhead
-
File Upload
- gRPC: Generate upload URL
- NATS: Trigger background processing
-
File Processing
- gRPC: Retrieve file metadata
- NATS: Distribute processing tasks
- Distributed tracing
- Message queue metrics
- Service health checks
- Performance logging
- Implement circuit breakers
- Add more sophisticated error handling
- Explore advanced NATS features (JetStream)
- Implement comprehensive logging
- Stream-based file processing
- Compressed file handling
- Metadata extraction
- Data transformation
- Flexible JSON processing
File Upload Complete
│
├── FileStorageService
│ ├── Compress File
│ └── Store Compressed File
│
└── FileProcessorService
├── Receive Processing Event
├── Retrieve Compressed File
├── Streaming Decompression
├── JSON Parsing
│ ├── Chunk-based Processing
│ ├── Memory Efficient Parsing
│ └── Configurable Extractors
│
├── Data Extraction
│ ├── Apply Predefined Rules
│ ├── Transform Data
│ └── Generate Metadata
│
└── Result Handling
├── Store Processed Metadata
└── Publish Completion Event
- Synchronous: gRPC for metadata retrieval
- Asynchronous: NATS for processing events
-
File Upload Completion
- FileStorageService compresses file
- Publishes file ready event to NATS
-
Processing Initiation
// NATS Event Structure type FileProcessEvent struct { FileID string Filename string StoragePath string Compression string // zstd, lz4 UploadedAt time.Time }
-
FileProcessorService Workflow
func ProcessFile(event FileProcessEvent) { // 1. Retrieve compressed file compressedFile := FileStorageService.RetrieveFile(event.StoragePath) // 2. Create streaming processor processor := NewStreamingJSONProcessor(compressedFile) // 3. Process file result, err := processor.Process() // 4. Handle processing result if err == nil { // Store metadata // Publish success event } else { // Publish failure event } }
- Streaming parser
- Chunk-based processing
- Configurable extractors
- Memory-efficient design
-
Decompression Stream
- Support for zstd/lz4
- Minimal memory overhead
- Efficient for large files
-
JSON Extractor
type JSONExtractor struct { Name string Path string Transformer func(interface{}) (interface{}, error) }
-
Transformation Rules
- Data normalization
- Sensitive information removal
- Custom transformation logic
type ProcessingMetadata struct {
FileID string
TotalRecords int
ProcessingTime time.Duration
ExtractedFields []string
CompressionMethod string
ProcessingStatus ProcessingStatus
}- Graceful error management
- Partial processing support
- Detailed error logging
- Event-based error notification
- Processing duration tracking
- Success/failure metrics
- Resource utilization monitoring
- Distributed tracing support
- Machine learning-based extraction
- More complex transformation rules
- Support for additional file types
- Advanced error recovery mechanisms
- Language: Go
- Parsing: Streaming JSON parser
- Compression: zstd, lz4
- Communication: gRPC, NATS
- Monitoring: Prometheus, OpenTelemetry
Our project adopts a Modular Monolith architectural approach, balancing simplicity with future extensibility. This design provides a pragmatic solution for our learning-focused project, offering:
- Simplified initial development
- Reduced operational complexity
- Clear internal boundaries
- Preparation for potential future microservices
The core service integrates multiple responsibilities:
- User management
- Authentication
- File upload processing
- Data storage handling
- Permission management
type CoreService struct {
UserManager *UserManagement
FileManager *FileOperations
StorageManager *StorageHandler
AuthManager *AuthenticationProvider
}-
Modularity
- Clear internal service boundaries
- Use of interfaces and composition
- Minimal coupling between components
-
Flexibility
- Prepare for potential future service extraction
- Implement dependency injection
- Design with scalability in mind
- Simplified initial implementation
- Lower operational complexity
- Faster inter-component communication
- Easier deployment and management
Current State: Single Service
[Core Service]
│
├── User Management
├── File Upload
├── Storage Handling
└── Authentication
Future Potential Microservices
[UserService] [FileUploadService] [FileStorageService]
- Increasing system complexity
- Divergent scaling requirements
- Performance bottlenecks
- Team growth and independent development needs
- Maintain clear internal module boundaries
- Use interfaces for component interactions
- Implement dependency injection
- Design with potential future decomposition in mind
// Unified interface for multiple operations
func (s *CoreService) ProcessUpload(
ctx context.Context,
user *User,
uploadRequest *UploadRequest,
) (*UploadResult, error) {
// Coordinate:
// 1. User authentication
// 2. Permission checking
// 3. File processing
// 4. Storage handling
// 5. Metadata generation
}The Modular Monolith approach provides a balanced, pragmatic solution for our project. It offers the benefits of a monolithic architecture while maintaining the flexibility to evolve into microservices if required.
Key Takeaway: Prioritize clear design and modularity over premature architectural complexity.
This section outlines the preliminary REST API design for frontend-backend communication.
- RESTful API design
- OpenAPI/Swagger specification
- Clear, predictable endpoints
- Flexible metadata handling
- File Upload Initiation
- File Upload Completion
- File Listing
- File Metadata Retrieval
- File Processing Trigger
- Detailed OpenAPI specification stored in
docs/openapi.yaml - Generated client SDKs will be available in future iterations
- Support for multiple frontend frameworks
- Easy integration with modern web technologies
- Minimal coupling between frontend and backend
- Comprehensive error handling
- Advanced filtering and pagination
- Potential GraphQL exploration
- Client SDK generation
- REST API
- OpenAPI 3.0
- JSON payload format
- HTTP/HTTPS communication
Note: This section is a placeholder and will be refined as the project evolves.
- Synchronous: gRPC
- Asynchronous: NATS
- Serialization: Protocol Buffers
- Go (Golang)
- gRPC-Go
- NATS Go Client
- OpenTelemetry (Tracing)
A deliberately simple, educational microservices architecture focusing on core distributed systems concepts. Our hybrid communication architecture provides a robust, flexible, and educational approach to building microservices, balancing simplicity with powerful communication patterns.