From wpf-dev-pack
Guides ErrorOr result pattern implementation for WPF MVVM service layer error handling without exceptions. Includes FluentValidation and CommunityToolkit.Mvvm integration.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
ErrorOr 2.x 기반 서비스 레이어 에러 처리 가이드.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
ErrorOr 2.x 기반 서비스 레이어 에러 처리 가이드.
<PackageReference Include="ErrorOr" Version="2.0.*" />
namespace MyApp.Core.Services;
public sealed class UserService(IUserRepository repository)
{
public ErrorOr<User> GetUser(int id)
{
var user = repository.FindById(id);
if (user is null)
return Error.NotFound("User.NotFound", "사용자를 찾을 수 없습니다.");
// User not found.
return user;
}
public ErrorOr<Created> CreateUser(string name, string email)
{
if (repository.ExistsByEmail(email))
return Error.Conflict("User.DuplicateEmail", "이미 사용 중인 이메일입니다.");
// Email is already in use.
repository.Add(new User(name, email));
return Result.Created;
}
}
public sealed partial class UserViewModel : ObservableObject
{
private readonly UserService _userService;
[ObservableProperty] private string _name = string.Empty;
[ObservableProperty] private string _errorMessage = string.Empty;
[ObservableProperty] private bool _hasError;
[RelayCommand]
private void LoadUser(int userId)
{
_userService.GetUser(userId).Switch(
user =>
{
HasError = false;
Name = user.Name;
},
errors =>
{
HasError = true;
ErrorMessage = errors.First().Description;
});
}
}
| Factory Method | 용도 |
|---|---|
Error.Failure() | 일반 실패 |
Error.Unexpected() | 예상치 못한 오류 |
Error.Validation() | 유효성 검증 실패 |
Error.Conflict() | 리소스 충돌 |
Error.NotFound() | 리소스 미발견 |
Error.Unauthorized() | 인증 실패 |
Error.Forbidden() | 권한 없음 |
// code, description, metadata 지정
// Specify code, description, metadata
var error = Error.Validation(
code: "User.InvalidAge",
description: "나이는 1~150 사이여야 합니다.",
// Age must be between 1 and 150.
metadata: new Dictionary<string, object> { ["Field"] = "Age" });
ErrorOr<string> result = _userService.GetUser(userId)
.Then(user => user.Email)
.FailIf(email => string.IsNullOrEmpty(email),
Error.Validation("User.NoEmail", "이메일이 없습니다."))
// User has no email.
.Then(email => email.ToUpperInvariant());
namespace MyApp.Core.Extensions;
public static class FluentValidationExtensions
{
public static List<Error> ToErrors(this FluentValidation.Results.ValidationResult result)
{
return result.Errors
.Select(e => Error.Validation(e.PropertyName, e.ErrorMessage))
.ToList();
}
}
public ErrorOr<Updated> UpdateUser(UpdateUserRequest request)
{
var validation = _validator.Validate(request);
if (!validation.IsValid)
return validation.ToErrors();
// 비즈니스 로직 처리
// Process business logic
_repository.Update(request.ToUser());
return Result.Updated;
}
┌─────────────────────────────────────────┐
│ ViewModel Layer │
│ ErrorOr<T>.Switch() → UI 상태 업데이트 │
│ ErrorOr<T>.Switch() → Update UI state │
├─────────────────────────────────────────┤
│ Service Layer │
│ return ErrorOr<T> (예외 대신 결과 반환) │
│ return ErrorOr<T> (result instead of │
│ exceptions) │
├─────────────────────────────────────────┤
│ Infrastructure Layer │
│ try-catch → Error 변환 │
│ try-catch → Convert to Error │
└─────────────────────────────────────────┘
// Infrastructure → Service 경계에서 예외 변환
// Convert exceptions at Infrastructure → Service boundary
public ErrorOr<User> GetUserFromApi(int id)
{
try
{
var user = _httpClient.GetFromJsonAsync<User>($"/api/users/{id}").Result;
return user ?? Error.NotFound("User.NotFound", "사용자를 찾을 수 없습니다.");
// User not found.
}
catch (HttpRequestException ex)
{
return Error.Unexpected("Api.Error", ex.Message);
}
}
| 실수 | 올바른 방법 |
|---|---|
IsError 확인 없이 .Value 접근 | Match/Switch로 양쪽 경로 처리 |
| 모든 곳에서 ErrorOr 사용 (과도한 적용) | Service Layer 경계에서만 사용 |
| Exception과 ErrorOr 혼용 | 레이어별 명확한 규칙 수립 |
빈 List<Error> 반환 | 에러 없으면 성공 값 반환 |
| Error 객체를 UI에 직접 바인딩 | Error.Description 문자열로 변환 후 바인딩 |
| ViewModel에서 성공만 처리 | Switch/Match로 에러 경로 필수 처리 |
ErrorOr<T> 사용 (예외 대신)Switch/Match로 양쪽 경로 필수 처리.ToErrors() 확장 메서드로 변환try-catch → Error 변환validating-with-fluentvalidation (폼 검증), implementing-wpf-validation (WPF 기본 검증)