Skip to main content

Command Palette

Search for a command to run...

[Stl.Fusion] Replica 서비스 (feat. HelloBlazorHybrid)

Published
5 min read

Stl.Fusion(이하 Fusion)은 실시간성 앱을 효율적으로 만들 수 있는 환경을 제공해주는 라이브러리입니다.

오늘 시간은 클라이언트에서 Replica 서비스를 이용해 Compute 서비스와 유사하게 무효화 및 캐시 기능을 사용하는 방법을 HelloBlazorHybrid 샘플을 분석하면서 진행하도록 하겠습니다.

Replica 서비스의 기본적인 이해는 Fusion 튜토리얼 - 4부: Replica 서비스를 참조하세요.

개요

Fusion분산 반응형 메모이제이션 환경을 제공해주는 라이브러리 또는 프레임워크 입니다. 이것을 Fusion에서는 DREAM이라고 줄여서 말합니다.

좀 더 상세한 설명을 Fusion 개요를 참조하세요.

또한 Fusion은 Blazor Server와 Webassembly간 단일 코드베이스를 구축할 수 있는 환경을 제공해 Blazor를 이용해 서비스를 만드는데 둘 중 어느 것을 사용해야 하는지 고민하지 않아도 됩니다.

Fusion 튜토리얼 - 6부: Blazor 앱의 실시간 UI 번역을 완료하면 이곳에 링크할 예정입니다.

Replica 서비스 (복제 서비스)를 이용하면 클라이언트로 노출해야 할 API를 Web API 형태로 그대로 사용할 수 있고 몇 가지 환경 구성을 통해 서버의 Compute 서비스를 클라이언트에서 DREAM의 이점을 그대로 활용하면서 사용할 수 있습니다.

샘플 정보

  • GitHub 레파지토리 : https://github.com/servicetitan/Stl.Fusion.Samples
  • HelloBlazorHybrid 샘플 : https://github.com/servicetitan/Stl.Fusion.Samples/tree/master/src/HelloBlazorHybrid
  • 개발 환경
    • Visual Studio 2022, C#, .NET 6

Replica 서비스

Fusion의 Replica 서비스는 Blazor Webassembly, 모바일 앱, 데스크톱 앱, 콘솔 등에서 Fusion으로 구성된 Compute 서비스를 클라이언트에서 동일하게 사용할 수 있도록 복제하는 서비스 입니다. 모든 환경 구성이 되면 사용법은 거의 동일하게 됩니다.

HelloBlazorHybrid 샘플을 분석하면서 Replica 서비스를 이해하도록 합시다.

HelloBlazorHybrid 샘플

HelloBlazorHybrid 프로젝트는 Fusion이 제공하는 기능을 Blazor에서 확인할 수 있도록 구성된 샘플입니다. 프로젝트는 Blazor Server 및 Webassembly에서 모두 동작하도록 구성되어 있습니다.

프로젝트 구성

  • Abstrations

    인터페이스 및 구조입니다. Blazor Server 및 Webassembly에서 공통으로 필요 합니다.

  • Server Blazor Server 및 Webassembly로 샘플을 호스팅합니다.

  • Services 서비스입니다. 호스팅 서비스 및 Compute 서비스가 있습니다.

  • UI Blazor의 UI 입니다. Blazor Server 및 Webassembly에서 공통으로 사용합니다.

Fusion 구성

Fusion을 사용하려면 Fusion 서비스를 추가해야 합니다.

| Server/Startup.cs, ConfigureServices()

...
        // Fusion
        var fusion = services.AddFusion();
        var fusionServer = fusion.AddWebServer();
        fusion.AddFusionTime(); // IFusionTime is one of built-in compute services you can use
        services.AddScoped<BlazorModeHelper>();

        // Fusion services
        fusion.AddComputeService<ICounterService, CounterService>();
        fusion.AddComputeService<IWeatherForecastService, WeatherForecastService>();
        fusion.AddComputeService<IChatService, ChatService>();
        fusion.AddComputeService<ChatBotService>();
        // This is just to make sure ChatBotService.StartAsync is called on startup
        services.AddHostedService(c => c.GetRequiredService<ChatBotService>());
...

위의 구성으로 서버 측의 Compute 서비스가 등록이 됩니다.

Compute 서비스 구현 및 사용법은 Fusion 튜토리얼 - 4부: Replica 서비스를 참조하세요.

        // Shared UI services
        UI.Program.ConfigureSharedServices(services);

UI 관련 서비스를 초기화 합니다. 위의 경우는 Blazor Server에서 사용 합니다. Blazor Webassembly의 경우 UI 프로젝트의 Program.csMain()에서 ConfigureServices() 에서 동일한 UI.Program.ConfigureSharedServices(services)를 호출함을 확인할 수 있습니다.

클라이언트(여기서는 웹브라우저에서 동작하는 Webassembly)에서 서버의 Compute 서비스를 호출하려면 Proxy 인터페이스를 통해 Web API를 호출하는 방법을 알아야 합니다.

| UI/Program.cs, ConfigureServices()

...
        // Fusion service clients
        fusionClient.AddReplicaService<ICounterService, ICounterClientDef>();
        fusionClient.AddReplicaService<IWeatherForecastService, IWeatherForecastClientDef>();
        fusionClient.AddReplicaService<IChatService, IChatClientDef>();
...

AddReplicaService()를 통해 서비스 인터페이스와 Web API가 연결이 됩니다. 물론 실제로 저 인터페이스로 호출하려면 인터페이스를 구현한 구현체가 있어야 하는데 Fusion은 이 Proxy 구현체를 런타임에서 만들어 인스턴스를 생성해줍니다. 이제 인터페이스를 통해 원격 API를 호출할 수 있게 되는 것입니다.

그렇다면 단순히 Proxy를 통해 Web API를 호출하는 것과 어떤 차이가 있을까요?

Replica 서비스의 동작

fusionClient.AddReplicaService<TService, TClient>에 의해 Replica 서비스가 등록되면 이제 마치 서비스 구현체의 인스턴스가 있는 것처럼 호출할 수 있게 됩니다.

| UI/Counter.razor

@inject ICounterService CounterService

razor에서 @inject으로 원하는 Replica 서비스의 인스턴스를 가져올 수 있습니다.

CounterService.Increment();

인터페이스에 맞는 메소드를 호출 할 수 있으며 Fusion에 의해 생성된 Proxy 구현체의 인스턴스에 의해 ICounterClientDef인터페이스에 정의된 규칙으로 Web API를 호출합니다.

| Abstractions/Clients.cs

[BasePath("counter")]
public interface ICounterClientDef
{
    [Post("increment")]
    Task Increment(CancellationToken cancellationToken = default);

    [Get("get")]
    Task<int> Get(CancellationToken cancellationToken = default);
}

특성에 의해 /api/counter/increment의 API가 호출이 되는데 이것은 다음의 컨트롤러에 의해 서버의 Compute 서비스를 사용합니다.

| Server/CounterController.cs

[Route("api/[controller]/[action]")]
[ApiController, JsonifyErrors, UseDefaultSession]
public class CounterController : ControllerBase, ICounterService
{
    private readonly ICounterService _counter;

    public CounterController(ICounterService counter) 
        => _counter = counter;

    [HttpGet, Publish]
    public Task<int> Get(CancellationToken cancellationToken = default)
        => _counter.Get(cancellationToken);

    [HttpPost]
    public Task Increment(CancellationToken cancellationToken = default)
        => _counter.Increment(cancellationToken);
}

서비스의 Get()의 특성 중 Publish가 있는데 클라이언트가 게시를 요청할 경우 Get 출력이 게시 되도록 합니다.

Replica 서비스의 흥미로운 점은 Replica 서비스 또한 상태가 무효화 되기 전까지 값을 캐시 한다는 점입니다. 서버의 상태가 무효화되지 않는 한 클라이언트에서 값을 읽은 후 다시 읽으려 할 때 서버에 요청하지 않고 캐시된 값을 사용하게 됩니다.

image.png

웹 브라우저에서 동일한 페이지를 여러 개 만들고 Increment 버튼을 눌렀을 때 Count가 동시에 올라가는 것을 볼 수 있습니다.

이번에는 IWeatherForecastService를 보시죠.

| Abstractions/IWeatherForcastService.cs

public interface IWeatherForecastService
{
    [ComputeMethod]
    Task<WeatherForecast[]> GetForecast(DateTime startDate, CancellationToken cancellationToken = default);
}

시작 날짜로 예보를 구하는 Compute 서비스 입니다.

| Services/WeatherForecastService.cs

public class WeatherForecastService : IWeatherForecastService
{
    private static readonly string[] Summaries = {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [ComputeMethod(AutoInvalidationDelay = 1)]
    public virtual Task<WeatherForecast[]> GetForecast(
        DateTime startDate, CancellationToken cancellationToken = default)
    {
        var rng = new Random();
        return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        }).ToArray());
    }
}

한 가지 다른 점이 있습니다. ComputeMethod() 특성에서 AutoInvalidationDelay가 1로 설정되어 있는데 1초마다 자동으로 무효화를 해주는 설정입니다. 이것으로 인해 매 초마다 예보 정보가 갱신 될 것입니다.

다음은 컨트롤러 입니다.

| Server/Controllers/WeatherForecastController.cs

[Route("api/[controller]/[action]")]
[ApiController, JsonifyErrors, UseDefaultSession]
public class WeatherForecastController : ControllerBase, IWeatherForecastService
{
    private readonly IWeatherForecastService _forecast;

    public WeatherForecastController(IWeatherForecastService forecast) 
        => _forecast = forecast;

    [HttpGet, Publish]
    public Task<WeatherForecast[]> GetForecast(DateTime startDate,
        CancellationToken cancellationToken = default)
        => _forecast.GetForecast(startDate, cancellationToken);
}

이 컨트롤러에 의해 /api/WeatherForecast/getForecastWeb API가 노출됩니다.

[BasePath("weatherForecast")]
public interface IWeatherForecastClientDef
{
    [Get("getForecast")]
    Task<WeatherForecast[]> GetForecast(DateTime startDate, CancellationToken cancellationToken = default);
}

fusionClient.AddReplicaService<IWeatherForecastService, IWeatherForecastClientDef>();에 의해 해당 Web API가 Replica 서비스로 연결이 됩니다.

image.png

정리

오늘은 Stl.Fusion에서 제공하는 HelloBlazorHybrid 샘플을 통해 Fusion의 Replica 서비스에 대해 알아 보았습니다. Replica 서비스를 이용하면 마치 내부 기능을 사용하는 것 처럼 서버의 Compute 서비스를 사용할 수 있게 되며 상태 무효화시 클라이언트까지 변경된 값이 잘 적용됨을 확인할 수 있습니다.

More from this blog

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

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

May 9, 20256 min read15

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

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

Mar 18, 20243 min read19

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

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

Mar 16, 20242 min read8

디모이 블로그

154 posts

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

[Stl.Fusion] Replica 서비스 (feat. HelloBlazorHybrid)