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

Aug 2, 2022·

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의 이점을 그대로 활용하면서 사용할 수 있습니다.

샘플 정보

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 서비스를 사용할 수 있게 되며 상태 무효화시 클라이언트까지 변경된 값이 잘 적용됨을 확인할 수 있습니다.