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 ๋ ํ์งํ ๋ฆฌ : GitHub - servicetitan/Stl.Fusion.Samples: A collection of samples for Fusion library: https://github.com/servicetitan/Stl.Fusion
- HelloBlazorHybrid ์ํ : Stl.Fusion.Samples/src/HelloBlazorHybrid at master ยท servicetitan/Stl.Fusion.Samples ยท GitHub
- ๊ฐ๋ฐ ํ๊ฒฝ
- 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.cs
์ Main()
์์ 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 ์๋น์ค ๋ํ ์ํ๊ฐ ๋ฌดํจํ ๋๊ธฐ ์ ๊น์ง ๊ฐ์ ์บ์ ํ๋ค๋ ์ ์ ๋๋ค. ์๋ฒ์ ์ํ๊ฐ ๋ฌดํจํ๋์ง ์๋ ํ ํด๋ผ์ด์ธํธ์์ ๊ฐ์ ์ฝ์ ํ ๋ค์ ์ฝ์ผ๋ ค ํ ๋ ์๋ฒ์ ์์ฒญํ์ง ์๊ณ ์บ์๋ ๊ฐ์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
์น ๋ธ๋ผ์ฐ์ ์์ ๋์ผํ ํ์ด์ง๋ฅผ ์ฌ๋ฌ ๊ฐ ๋ง๋ค๊ณ 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/getForecast
์ Web API
๊ฐ ๋
ธ์ถ๋ฉ๋๋ค.
[BasePath("weatherForecast")]
public interface IWeatherForecastClientDef
{
[Get("getForecast")]
Task<WeatherForecast[]> GetForecast(DateTime startDate, CancellationToken cancellationToken = default);
}
fusionClient.AddReplicaService<IWeatherForecastService, IWeatherForecastClientDef>();
์ ์ํด ํด๋น Web API
๊ฐ Replica ์๋น์ค๋ก ์ฐ๊ฒฐ์ด ๋ฉ๋๋ค.
์ ๋ฆฌ
์ค๋์ Stl.Fusion
์์ ์ ๊ณตํ๋ HelloBlazorHybrid ์ํ
์ ํตํด Fusion์ Replica ์๋น์ค์ ๋ํด ์์ ๋ณด์์ต๋๋ค. Replica ์๋น์ค๋ฅผ ์ด์ฉํ๋ฉด ๋ง์น ๋ด๋ถ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ ์ฒ๋ผ ์๋ฒ์ Compute ์๋น์ค๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ฉฐ ์ํ ๋ฌดํจํ์ ํด๋ผ์ด์ธํธ๊น์ง ๋ณ๊ฒฝ๋ ๊ฐ์ด ์ ์ ์ฉ๋จ์ ํ์ธํ ์ ์์ต๋๋ค.