Integrate Notification Service into Tender Management System
- Added a new notification service to handle various notification types (email, SMS, push, OTP). - Implemented configuration for the notification service, allowing customization via environment variables. - Updated user and customer services to utilize the notification service for sending welcome emails and status updates. - Introduced a builder pattern for constructing notification requests, enhancing usability and flexibility. - Enhanced error handling and logging for notification operations, ensuring robust service integration. - Updated documentation to include usage examples and configuration details for the new notification SDK.
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
# Notification SDK
|
||||
|
||||
A comprehensive Go SDK for interacting with the Tender Management notification service. This SDK provides a clean, type-safe interface for sending various types of notifications including emails, SMS, push notifications, and OTP messages.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Notification Types**: Support for EMAIL, SMS, PUSH, and OTP notifications
|
||||
- **Automatic Retries**: Configurable retry mechanism with exponential backoff
|
||||
- **Structured Logging**: Built-in logging with structured fields
|
||||
- **Fluent API**: Builder pattern for constructing complex notifications
|
||||
- **Batch Operations**: Send multiple notifications efficiently
|
||||
- **Type Safety**: Full type safety with comprehensive validation
|
||||
- **Error Handling**: Detailed error types and proper error wrapping
|
||||
- **Configuration**: Flexible configuration via environment variables or code
|
||||
- **Health Checks**: Built-in health checking capabilities
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get your-module/pkg/notification
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"your-module/pkg/notification"
|
||||
"your-module/pkg/logger" // Your logger implementation
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create configuration
|
||||
config := notification.DefaultConfig()
|
||||
config.BaseURL = "http://127.0.0.1:9095" // Your notification service URL
|
||||
|
||||
// Create logger (implement notification.Logger interface)
|
||||
logger := logger.New() // Your logger implementation
|
||||
|
||||
// Create SDK
|
||||
sdk, err := notification.NewSDK(config, logger)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create notification SDK:", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Send a simple email
|
||||
err = sdk.QuickEmail(ctx, "user@example.com", "Hello World!")
|
||||
if err != nil {
|
||||
log.Printf("Failed to send email: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Set environment variables for automatic configuration:
|
||||
|
||||
```bash
|
||||
export NOTIFICATION_BASE_URL="http://127.0.0.1:9095"
|
||||
export NOTIFICATION_TIMEOUT="30s"
|
||||
export NOTIFICATION_RETRY_ATTEMPTS="3"
|
||||
export NOTIFICATION_RETRY_DELAY="2s"
|
||||
export NOTIFICATION_ENABLE_LOGGING="true"
|
||||
export NOTIFICATION_USER_AGENT="MyApp-NotificationSDK/1.0"
|
||||
```
|
||||
|
||||
Then load configuration:
|
||||
|
||||
```go
|
||||
import "your-module/pkg/config"
|
||||
|
||||
// Load configuration from environment
|
||||
cfg, err := config.LoadConfig(".", ¬ification.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load config:", err)
|
||||
}
|
||||
|
||||
sdk, err := notification.NewSDK(cfg, logger)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
BaseURL string `env:"NOTIFICATION_BASE_URL"`
|
||||
Timeout time.Duration `env:"NOTIFICATION_TIMEOUT"`
|
||||
RetryAttempts int `env:"NOTIFICATION_RETRY_ATTEMPTS"`
|
||||
RetryDelay time.Duration `env:"NOTIFICATION_RETRY_DELAY"`
|
||||
EnableLogging bool `env:"NOTIFICATION_ENABLE_LOGGING"`
|
||||
UserAgent string `env:"NOTIFICATION_USER_AGENT"`
|
||||
}
|
||||
```
|
||||
|
||||
### Creating SDK Instance
|
||||
|
||||
```go
|
||||
// With default configuration
|
||||
sdk, err := notification.NewSDK(nil, logger)
|
||||
|
||||
// With custom configuration
|
||||
config := ¬ification.Config{
|
||||
BaseURL: "http://localhost:9095",
|
||||
Timeout: 30 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 2 * time.Second,
|
||||
EnableLogging: true,
|
||||
UserAgent: "MyApp/1.0",
|
||||
}
|
||||
sdk, err := notification.NewSDK(config, logger)
|
||||
|
||||
// With custom HTTP client
|
||||
httpClient := &http.Client{Timeout: 10 * time.Second}
|
||||
sdk, err := notification.NewSDKWithClient(config, httpClient, logger)
|
||||
```
|
||||
|
||||
### Sending Notifications
|
||||
|
||||
#### Simple Notifications
|
||||
|
||||
```go
|
||||
ctx := context.Background()
|
||||
|
||||
// Email
|
||||
response, err := sdk.SendEmail(ctx, "user@example.com", "Welcome!", "user123")
|
||||
|
||||
// SMS
|
||||
response, err := sdk.SendSMS(ctx, "+1234567890", "Your code: 1234", "user123")
|
||||
|
||||
// Push Notification
|
||||
response, err := sdk.SendPush(ctx, "device_token_here", "New message!", "user123")
|
||||
|
||||
// OTP
|
||||
response, err := sdk.SendOTP(ctx, "+1234567890", "Your OTP: 5678", "user123")
|
||||
```
|
||||
|
||||
#### Quick Notifications (without user ID)
|
||||
|
||||
```go
|
||||
// Quick methods for simple use cases
|
||||
err := sdk.QuickEmail(ctx, "user@example.com", "Hello!")
|
||||
err := sdk.QuickSMS(ctx, "+1234567890", "Hello!")
|
||||
err := sdk.QuickPush(ctx, "device_token", "Hello!")
|
||||
err := sdk.QuickOTP(ctx, "+1234567890", "Code: 1234")
|
||||
```
|
||||
|
||||
#### Custom Notifications
|
||||
|
||||
```go
|
||||
request := ¬ification.NotificationRequest{
|
||||
EventType: notification.EventTypeEmail,
|
||||
Message: "Custom notification message",
|
||||
Methods: notification.NotificationMethods{
|
||||
Email: "user@example.com",
|
||||
SMS: "+1234567890", // Optional: multiple delivery methods
|
||||
},
|
||||
UserID: "user123",
|
||||
}
|
||||
|
||||
response, err := sdk.SendNotification(ctx, request)
|
||||
```
|
||||
|
||||
### Fluent Builder API
|
||||
|
||||
```go
|
||||
// Build and send notification using fluent API
|
||||
response, err := sdk.NewBuilder().
|
||||
SetEventType(notification.EventTypeEmail).
|
||||
SetMessage("Welcome to our service!").
|
||||
SetEmail("user@example.com").
|
||||
SetUserID("user123").
|
||||
Send(ctx)
|
||||
|
||||
// Build without sending
|
||||
request, err := sdk.NewBuilder().
|
||||
SetEventType(notification.EventTypeSMS).
|
||||
SetMessage("Your verification code: 1234").
|
||||
SetSMS("+1234567890").
|
||||
SetUserID("user123").
|
||||
Build()
|
||||
|
||||
if err == nil {
|
||||
response, err := sdk.SendNotification(ctx, request)
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```go
|
||||
// Prepare multiple requests
|
||||
requests := []*notification.NotificationRequest{
|
||||
{
|
||||
EventType: notification.EventTypeEmail,
|
||||
Message: "Welcome email",
|
||||
Methods: notification.NotificationMethods{Email: "user1@example.com"},
|
||||
UserID: "user1",
|
||||
},
|
||||
{
|
||||
EventType: notification.EventTypeSMS,
|
||||
Message: "Welcome SMS",
|
||||
Methods: notification.NotificationMethods{SMS: "+1234567890"},
|
||||
UserID: "user2",
|
||||
},
|
||||
}
|
||||
|
||||
// Send batch
|
||||
batchResponse, err := sdk.SendBatch(ctx, requests)
|
||||
if err != nil {
|
||||
log.Printf("Batch failed: %v", err)
|
||||
} else {
|
||||
log.Printf("Batch completed: %d success, %d failed",
|
||||
batchResponse.Success, batchResponse.Failed)
|
||||
|
||||
// Check individual results
|
||||
for _, resp := range batchResponse.Responses {
|
||||
if !resp.Success {
|
||||
log.Printf("Request %d failed: %s", resp.Index, resp.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
```go
|
||||
// Check if notification service is available
|
||||
err := sdk.Health(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Notification service is unhealthy: %v", err)
|
||||
} else {
|
||||
log.Println("Notification service is healthy")
|
||||
}
|
||||
```
|
||||
|
||||
## Event Types
|
||||
|
||||
The SDK supports the following notification types:
|
||||
|
||||
- `notification.EventTypeEmail` - Email notifications
|
||||
- `notification.EventTypeSMS` - SMS notifications
|
||||
- `notification.EventTypePush` - Push notifications
|
||||
- `notification.EventTypeOTP` - OTP notifications
|
||||
|
||||
## Error Handling
|
||||
|
||||
The SDK provides detailed error types for different scenarios:
|
||||
|
||||
```go
|
||||
response, err := sdk.SendEmail(ctx, "invalid-email", "Hello!", "user123")
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case notification.ErrHTTP:
|
||||
log.Printf("HTTP error %d: %s", e.StatusCode, e.Message)
|
||||
case notification.ErrValidation:
|
||||
log.Printf("Validation error in field %s: %s", e.Field, e.Message)
|
||||
case notification.ErrRetryExhausted:
|
||||
log.Printf("Retry exhausted after %d attempts: %v", e.Attempts, e.LastError)
|
||||
case notification.ErrInvalidConfig:
|
||||
log.Printf("Configuration error in field %s: %s", e.Field, e.Message)
|
||||
default:
|
||||
log.Printf("Unknown error: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
The SDK requires a logger that implements the `notification.Logger` interface:
|
||||
|
||||
```go
|
||||
type Logger interface {
|
||||
Info(msg string, fields map[string]interface{})
|
||||
Error(msg string, fields map[string]interface{})
|
||||
Debug(msg string, fields map[string]interface{})
|
||||
Warn(msg string, fields map[string]interface{})
|
||||
}
|
||||
```
|
||||
|
||||
Example logger implementation with logrus:
|
||||
|
||||
```go
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
type LogrusLogger struct {
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Info(msg string, fields map[string]interface{}) {
|
||||
l.logger.WithFields(fields).Info(msg)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Error(msg string, fields map[string]interface{}) {
|
||||
l.logger.WithFields(fields).Error(msg)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Debug(msg string, fields map[string]interface{}) {
|
||||
l.logger.WithFields(fields).Debug(msg)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Warn(msg string, fields map[string]interface{}) {
|
||||
l.logger.WithFields(fields).Warn(msg)
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The SDK is designed to be easily testable. You can mock the HTTP client:
|
||||
|
||||
```go
|
||||
import (
|
||||
"testing"
|
||||
"net/http"
|
||||
"your-module/pkg/notification"
|
||||
)
|
||||
|
||||
// Mock HTTP client
|
||||
type MockHTTPClient struct {
|
||||
DoFunc func(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
return m.DoFunc(req)
|
||||
}
|
||||
|
||||
// Mock logger
|
||||
type MockLogger struct{}
|
||||
func (l *MockLogger) Info(msg string, fields map[string]interface{}) {}
|
||||
func (l *MockLogger) Error(msg string, fields map[string]interface{}) {}
|
||||
func (l *MockLogger) Debug(msg string, fields map[string]interface{}) {}
|
||||
func (l *MockLogger) Warn(msg string, fields map[string]interface{}) {}
|
||||
|
||||
func TestSendEmail(t *testing.T) {
|
||||
mockClient := &MockHTTPClient{
|
||||
DoFunc: func(req *http.Request) (*http.Response, error) {
|
||||
// Return mock response
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader(`{"status":"Notification sent"}`)),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
config := notification.DefaultConfig()
|
||||
logger := &MockLogger{}
|
||||
|
||||
sdk, err := notification.NewSDKWithClient(config, mockClient, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
response, err := sdk.SendEmail(context.Background(), "test@example.com", "Test", "user123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if response.Status != "Notification sent" {
|
||||
t.Errorf("Expected 'Notification sent', got %s", response.Status)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Configuration**: Use environment variables for configuration in production
|
||||
2. **Context**: Always pass context for timeout and cancellation support
|
||||
3. **Error Handling**: Check specific error types for appropriate handling
|
||||
4. **Logging**: Implement structured logging for better observability
|
||||
5. **Retries**: Configure appropriate retry settings based on your use case
|
||||
6. **Validation**: Let the SDK handle validation, but provide clean input data
|
||||
7. **Batch Operations**: Use batch operations for sending multiple notifications efficiently
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples/` directory for complete working examples:
|
||||
|
||||
- `basic_usage.go` - Basic notification sending
|
||||
- `batch_operations.go` - Batch notification processing
|
||||
- `fluent_api.go` - Using the builder pattern
|
||||
- `error_handling.go` - Comprehensive error handling
|
||||
- `configuration.go` - Different configuration methods
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Add tests for new functionality
|
||||
4. Ensure all tests pass
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
This SDK is part of the Tender Management system and follows the same license terms.
|
||||
Reference in New Issue
Block a user