edge-connect-client/internal/core/services/instance_service.go
Stephan Lo f3ac644813 feat: implement unified domain error handling system
Addresses Verbesserungspotential 2 (Error Handling uneinheitlich) by introducing
a comprehensive, structured error handling approach across all architectural layers.

## New Domain Error System
- Add ErrorCode enum with 15 semantic error types (NOT_FOUND, VALIDATION_FAILED, etc.)
- Implement DomainError struct with operation context, resource identifiers, and regions
- Create resource-specific error constructors (NewAppError, NewInstanceError, NewCloudletError)
- Add utility functions for error type checking (IsNotFoundError, IsValidationError, etc.)

## Service Layer Enhancements
- Replace generic fmt.Errorf with structured domain errors in all services
- Add comprehensive validation functions for App, AppInstance, and Cloudlet entities
- Implement business logic validation with meaningful error context
- Ensure consistent error semantics across app_service, instance_service, cloudlet_service

## Adapter Layer Updates
- Update EdgeConnect adapters to use domain errors instead of error constants
- Enhance CLI adapter with domain-specific error checking for better UX
- Fix SDK examples to use new IsNotFoundError() approach
- Maintain backward compatibility where possible

## Test Coverage
- Add comprehensive error_test.go with 100% coverage of new error system
- Update existing adapter tests to validate domain error types
- All tests passing with proper error type assertions

## Benefits
-  Consistent error handling across all architectural layers
-  Rich error context with operation, resource, and region information
-  Type-safe error checking with semantic error codes
-  Better user experience with domain-specific error messages
-  Maintainable centralized error definitions
-  Full hexagonal architecture compliance

Files modified: 12 files updated, 2 new files added
Tests: All passing (29+ test cases with enhanced error validation)
2025-10-08 16:52:36 +02:00

177 lines
5.7 KiB
Go

package services
import (
"context"
"strings"
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain"
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/ports/driven"
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/ports/driving"
)
type appInstanceService struct {
appInstanceRepo driven.AppInstanceRepository
}
func NewAppInstanceService(appInstanceRepo driven.AppInstanceRepository) driving.AppInstanceService {
return &appInstanceService{appInstanceRepo: appInstanceRepo}
}
func (s *appInstanceService) CreateAppInstance(ctx context.Context, region string, appInst *domain.AppInstance) error {
if err := s.validateAppInstance(appInst); err != nil {
return err
}
if region == "" {
return domain.ErrMissingRegion
}
if err := s.appInstanceRepo.CreateAppInstance(ctx, region, appInst); err != nil {
if domain.IsNotFoundError(err) {
return domain.NewInstanceError(domain.ErrResourceConflict, "CreateAppInstance", appInst.Key, region,
"app instance may already exist or have conflicting configuration")
}
return domain.NewInstanceError(domain.ErrInternalError, "CreateAppInstance", appInst.Key, region,
"failed to create app instance").WithDetails(err.Error())
}
return nil
}
func (s *appInstanceService) ShowAppInstance(ctx context.Context, region string, appInstKey domain.AppInstanceKey) (*domain.AppInstance, error) {
if err := s.validateAppInstanceKey(appInstKey); err != nil {
return nil, err
}
if region == "" {
return nil, domain.ErrMissingRegion
}
instance, err := s.appInstanceRepo.ShowAppInstance(ctx, region, appInstKey)
if err != nil {
if domain.IsNotFoundError(err) {
return nil, domain.NewInstanceError(domain.ErrResourceNotFound, "ShowAppInstance", appInstKey, region,
"app instance does not exist")
}
return nil, domain.NewInstanceError(domain.ErrInternalError, "ShowAppInstance", appInstKey, region,
"failed to retrieve app instance").WithDetails(err.Error())
}
return instance, nil
}
func (s *appInstanceService) ShowAppInstances(ctx context.Context, region string, appInstKey domain.AppInstanceKey) ([]domain.AppInstance, error) {
if region == "" {
return nil, domain.ErrMissingRegion
}
instances, err := s.appInstanceRepo.ShowAppInstances(ctx, region, appInstKey)
if err != nil {
return nil, domain.NewInstanceError(domain.ErrInternalError, "ShowAppInstances", appInstKey, region,
"failed to list app instances").WithDetails(err.Error())
}
return instances, nil
}
func (s *appInstanceService) DeleteAppInstance(ctx context.Context, region string, appInstKey domain.AppInstanceKey) error {
if err := s.validateAppInstanceKey(appInstKey); err != nil {
return err
}
if region == "" {
return domain.ErrMissingRegion
}
if err := s.appInstanceRepo.DeleteAppInstance(ctx, region, appInstKey); err != nil {
if domain.IsNotFoundError(err) {
return domain.NewInstanceError(domain.ErrResourceNotFound, "DeleteAppInstance", appInstKey, region,
"app instance does not exist")
}
return domain.NewInstanceError(domain.ErrInternalError, "DeleteAppInstance", appInstKey, region,
"failed to delete app instance").WithDetails(err.Error())
}
return nil
}
func (s *appInstanceService) UpdateAppInstance(ctx context.Context, region string, appInst *domain.AppInstance) error {
if err := s.validateAppInstance(appInst); err != nil {
return err
}
if region == "" {
return domain.ErrMissingRegion
}
if err := s.appInstanceRepo.UpdateAppInstance(ctx, region, appInst); err != nil {
if domain.IsNotFoundError(err) {
return domain.NewInstanceError(domain.ErrResourceNotFound, "UpdateAppInstance", appInst.Key, region,
"app instance does not exist")
}
return domain.NewInstanceError(domain.ErrInternalError, "UpdateAppInstance", appInst.Key, region,
"failed to update app instance").WithDetails(err.Error())
}
return nil
}
func (s *appInstanceService) RefreshAppInstance(ctx context.Context, region string, appInstKey domain.AppInstanceKey) error {
if err := s.validateAppInstanceKey(appInstKey); err != nil {
return err
}
if region == "" {
return domain.ErrMissingRegion
}
if err := s.appInstanceRepo.RefreshAppInstance(ctx, region, appInstKey); err != nil {
if domain.IsNotFoundError(err) {
return domain.NewInstanceError(domain.ErrResourceNotFound, "RefreshAppInstance", appInstKey, region,
"app instance does not exist")
}
return domain.NewInstanceError(domain.ErrInternalError, "RefreshAppInstance", appInstKey, region,
"failed to refresh app instance").WithDetails(err.Error())
}
return nil
}
// validateAppInstance performs business logic validation on an app instance
func (s *appInstanceService) validateAppInstance(appInst *domain.AppInstance) error {
if appInst == nil {
return domain.NewDomainError(domain.ErrValidationFailed, "app instance cannot be nil")
}
if err := s.validateAppInstanceKey(appInst.Key); err != nil {
return err
}
// Validate flavor if present
if strings.TrimSpace(appInst.Flavor.Name) == "" {
return domain.NewDomainError(domain.ErrValidationFailed, "flavor name is required")
}
return nil
}
// validateAppInstanceKey validates an app instance key
func (s *appInstanceService) validateAppInstanceKey(key domain.AppInstanceKey) error {
if strings.TrimSpace(key.Organization) == "" {
return domain.ErrInvalidInstanceKey.WithDetails("organization is required")
}
if strings.TrimSpace(key.Name) == "" {
return domain.ErrInvalidInstanceKey.WithDetails("name is required")
}
// Validate embedded cloudlet key
if strings.TrimSpace(key.CloudletKey.Organization) == "" {
return domain.ErrInvalidInstanceKey.WithDetails("cloudlet organization is required")
}
if strings.TrimSpace(key.CloudletKey.Name) == "" {
return domain.ErrInvalidInstanceKey.WithDetails("cloudlet name is required")
}
return nil
}