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)
177 lines
5.7 KiB
Go
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
|
|
}
|