Skip to main content

Command Palette

Search for a command to run...

Fusion 치트 시트

Updated
4 min read

컴퓨팅(Compute) 서비스

컴퓨팅 서비스 인터페이스:

// IComputeService는 선택적 태깅 인터페이스일 뿐입니다.
// 그럼에도 불구하고 "구현"하는 것이 좋습니다. 이를 통해 .GetServices() 및
// .GetCommander() 와 같은 몇 가지 확장 메서드를 사용할 수 있습니다.
public interface ICartService : IComputeService
{
    // 인터페이스 메소드에 적용할 때 이 속성은 구현에 의해 "상속"됩니다.
    [ComputeMethod]
    Task<List<Order>> GetOrders(long cartId, CancellationToken cancellationToken = default);
}

컴퓨팅 서비스 구현:

public class CartService : ICartService 
{
    // 메서드는 virtual 이어야 하며 Task<T>를 반환해야 합니다.
    public virtual async Task<List<Order>> GetOrders(long cartId, CancellationToken cancellationToken)
    {
        // 여기에 구현
    }
}

무효화 로직을 추가하세요 (아래 using 블록 내에서 무효화된 내용에 대한 결과를 변경하는 코드)

using (Computed.Invalidate()) {
// 이 블록 내에서 수행하는 모든 계산 메서드 호출은 호출을 무효화합니다.
// 실제 메서드 코드를 실행하는 대신 이 호출의 결과를 무효화합니다.
// 항상 동기식으로 완료되며 여기에서 CancellationToken 대신 "default"를 전달할 수 있습니다.
    _ = GetOrders(cartId, default);
}

컴퓨팅 서비스 등록:

fusion = services.AddFusion(); // services는 IServiceCollection
fusion.AddComputeService<IOrderService, OrderService>();

복제(Replica) 서비스

컨트롤러 추가:

[Route("api/[controller]/[action]")]
[ApiController, JsonifyErrors, UseDefaultSession]
public class CartController : ControllerBase, ICartService
{
    private readonly ICartService _cartService;
    private readonly ICommander _commander;

    public CartController(ICartService service, ICommander commander) 
    {
        _service = service;
        _commander = commander;
    }    

    [HttpGet, Publish]
    public Task<List<Order>> GetOrders(long cartId, CancellationToken cancellationToken)
        => _service.GetOrders(cartId, cancellationToken);
}

클라이언트 정의 추가:

[BasePath("cart")]
public interface ICartClientDef
{
    [Get(nameof(GetOrders))]
    Task<List<Order>> GetOrders(long cartId, CancellationToken cancellationToken);
}

Fusion 클라이언트 구성(클라이언트 측 IServiceProvider를 구성하는 코드에서 한 번만 수행해야 함):

var baseUri = new Uri("http://localhost:5005");
var apiBaseUri = new Uri($"{baseUri}api/");

var fusion = services.AddFusion();
fusion.AddRestEaseClient(
    client => {
        client.ConfigureWebSocketChannel(_ => new() { BaseUri = baseUri });
        client.ConfigureHttpClient((_, name, o) => {
            var isFusionClient = (name ?? "").StartsWith("Stl.Fusion");
            var clientBaseUri = isFusionClient ? baseUri : apiBaseUri;
            o.HttpClientActions.Add(httpClient => httpClient.BaseAddress = clientBaseUri);
        });
    });

복제 서비스 등록:

// "var fusionClient = ..." 이후
fusionClient.AddReplicaService<ITodoService, ITodoClientDef>();

복제 서비스 사용:

// 기존과 동일하게 그냥 호출하면 됩니다.
// 동일한 인수를 사용하여 이전 호출과 동일한 결과를 생성할 것으로 
// 예상되는 모든 호출은 로컬로 캐시된 IComputed를 통해 해결됩니다.
// IComputed가 서버에서 무효화되면 Fusion은 모든 클라이언트에서
// 해당 복제본을 무효화합니다.

명령 처리자(Commander)

명령 유형 선언:

// record를 사용할 필요는 없지만 계산 방법의 명령 및 출력에 변경 불가능한 유형을 사용하는 것이 좋습니다.
public record UpdateCartCommand(long CartId, Dictionary<long, long?> Updates) 
    : ICommand<Unit> // 단위는 명령의 반환 유형입니다. 다른 것을 사용할 수 있습니다
{
    // 호환성: Newtonsoft.Json 은 record를 역직렬화하려면 이 생성자가 필요합니다.
    public UpdateCartCommand() : this(0, null!) { }
}

컴퓨팅 서비스 인터페이스에 명령 처리기를 추가:

public interface ICartService : IComputeService
{
    // ...
    [CommandHandler] // 이 속성은 또한 impl에 의해 "상속"됩니다.
    Task<Unit> UpdateCart(UpdateCartCommand command, CancellationToken cancellationToken = default);

명령 처리기 구현 추가:

public class CartService : ICartService 
{
    // virtual이면서 ICommand<T>에 대해 Task<T>를 반환해야 함;
    // 명령은 첫 번째 인수여야 합니다. 다른 인수는 직접 전달되는 CancellationToken을 제외하고 DI 컨테이너에서 확인됩니다.
    public virtual Task<Unit> UpdateCart(UpdateCartCommand command, CancellationToken cancellationToken) 
    {
        if (Computed.IsInvalidating()) {
            // 여기에 이 명령에 대한 무효화 로직을 작성하세요.
            //
            // Fusion에 의해 등록된 명령 처리기 세트는 "정상" 로직이
            // 성공적으로 완료하면 무효화 블록 내에서 이 처리기를 "재시도" 합니다.
            // 또한 다중 호스트 무효화를 사용하는 경우
            // 클러스터의 모든 노드에서 이 블록을 실행합니다.
            return default;
        }

        // 명령어 처리기 코드는 여기에 있습니다.
    }

명령어 처리기 등록:

// 컴퓨팅 서비스 내에서 선언된 핸들러에는 아무 것도 필요하지 않습니다.

복제(Replica) 서비스를 통해 클라이언트에 명령 노출

명령에 대한 컨트롤러 메서드를 추가:

public class CartController : ControllerBase, ICartService
{
    // 항상 [HttpPost] 과 [FromBody] 를 사용
    [HttpPost]
    public Task<Unit> UpdateCart([FromBody] UpdateCartCommand command, CancellationToken cancellationToken)
        // 여기에서 해당 서비스 메서드를 직접 호출할 수도 있지만
        // CommanderOptions.AllowDirectCommandHandlerCalls = false를 사용하는 경우
        // 이 "스타일" 명령 호출만 작동합니다.
        // 참고로 둘은 동일하게 작동합니다. 즉, 명령 처리기가 직접 호출될 때 호출은
        // 여전히 ICommander를 통해 라우팅됩니다.
        => _commander.Call(command, cancellationToken);
}

클라이언트 정의 인터페이스에 명령 처리기를 추가:

public interface ICartClientDef 
{
    // 항상 [Post] 와 [Body] 를 사용
    [Post(nameof(UpdateCart))]
    Task<Unit> UpdateCart([Body] UpdateCartCommand command, CancellationToken cancellationToken);

클라이언트 측 명령 처리기 등록:

// 복제 서비스 내에서 선언된 핸들러에는 아무것도 필요하지 않습니다.
``


## `IComputed` 동작

캡처:
```chsarp
var computed = await Computed.Capture(ct => service.ComputeMethod(args, ct), cancellationToken);

IComputed가 여전히 일관되는지 확인:

if (computed.IsConsistent()) {
    // ...
}

무효화 대기:

// 항상 여기에 CancellationToken을 전달하십시오. 그렇지 않으면
// 이벤트 처리기 등록 수가 증가하여 메모리 누수가 발생하게 됩니다.
// 물론 무효화될 것이지만, 그런 일이 일어나지 않는다면 어떻게 될까요?
await computed.WhenInvalidated(cancellationToken);
// 또는
computed.Invalidated += c => Console.WriteLine("Invalidated!");

계속됩니다.

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read17

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read20

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.