84 lines
2.3 KiB
Go
84 lines
2.3 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
fwingestion "github.com/yourorg/go-dw-platform/framework/ingestion"
|
||
|
|
fwmetrics "github.com/yourorg/go-dw-platform/framework/metrics"
|
||
|
|
|
||
|
|
"github.com/yourorg/go-dw-platform/services/template-service/domain"
|
||
|
|
"github.com/yourorg/go-dw-platform/services/template-service/dto"
|
||
|
|
"github.com/yourorg/go-dw-platform/services/template-service/entity"
|
||
|
|
"github.com/yourorg/go-dw-platform/services/template-service/repository"
|
||
|
|
"github.com/yourorg/go-dw-platform/services/template-service/transformer"
|
||
|
|
)
|
||
|
|
|
||
|
|
var ErrDuplicate = errors.New("duplicate order")
|
||
|
|
|
||
|
|
type Service struct {
|
||
|
|
repository *repository.Repository
|
||
|
|
transformer *transformer.Transformer
|
||
|
|
batcher *fwingestion.Batcher[*domain.Order]
|
||
|
|
retrier *fwingestion.Retrier
|
||
|
|
}
|
||
|
|
|
||
|
|
func New(repo *repository.Repository, tf *transformer.Transformer, batchSize int, batchTimeout time.Duration, maxRetries int, retryDelay time.Duration) *Service {
|
||
|
|
return &Service{
|
||
|
|
repository: repo,
|
||
|
|
transformer: tf,
|
||
|
|
batcher: fwingestion.NewBatcher[*domain.Order](batchSize, batchTimeout),
|
||
|
|
retrier: fwingestion.NewRetrier(maxRetries, retryDelay),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Service) Process(ctx context.Context, req *dto.IngestRequest) (*dto.ProcessResult, error) {
|
||
|
|
order, err := s.transformer.RequestToDomain(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("process: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
exists, err := s.repository.ExistsByKey(ctx, order.NaturalKey())
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("check duplicate: %w", err)
|
||
|
|
}
|
||
|
|
if exists {
|
||
|
|
return nil, ErrDuplicate
|
||
|
|
}
|
||
|
|
|
||
|
|
s.batcher.Add(order)
|
||
|
|
|
||
|
|
if s.batcher.IsFull() || s.batcher.IsExpired() {
|
||
|
|
return s.FlushBatch(ctx)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &dto.ProcessResult{Status: "queued"}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Service) FlushBatch(ctx context.Context) (*dto.ProcessResult, error) {
|
||
|
|
start := time.Now()
|
||
|
|
batch := s.batcher.Get()
|
||
|
|
|
||
|
|
entities := make([]*entity.Order, len(batch))
|
||
|
|
for i, d := range batch {
|
||
|
|
entities[i] = s.transformer.DomainToEntity(d)
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := s.retrier.Do(ctx, func() (int64, error) {
|
||
|
|
return s.repository.InsertBatch(ctx, entities)
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("flush batch: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fwmetrics.RecordBatchInsert(len(batch), time.Since(start))
|
||
|
|
|
||
|
|
s.batcher.Clear()
|
||
|
|
return &dto.ProcessResult{
|
||
|
|
Status: "success",
|
||
|
|
RowsInserted: rows,
|
||
|
|
}, nil
|
||
|
|
}
|