Fusion - slog

Fusion์— ๋Œ€ํ•ด์„œ ์žฅ๊ธฐ ์Šฌ๋กœ๊น…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ข… ๋ชฉํ‘œ:

  • Fusion์„ ์ด์šฉํ•œ ๋‹ค์–‘ํ•œ ์ƒ˜ํ”Œ ๊ตฌ์„ฑ
  • Fusion ๊ด€๋ จ ๊ธฐ์ˆ  ๋ฌธ์„œ ๋ฐ ๋ฐœํ‘œ์ž๋ฃŒ
  • Fusion GitHub ํ™œ๋™์— ์ฐธ์—ฌ

โ€ป ์˜ฌํ•ด ๋‚ด๋‚ด ์ง„ํ–‰ํ•  ์žฅ๊ธฐ ์Šฌ๋กœ๊น… ์ž…๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

๋จผ์ € ๋ญ”๊ฐ€๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋Š”๊ฒŒ ๋น ๋ฆ…๋‹ˆ๋‹ค.

git clone GitHub - servicetitan/Stl.Fusion.Samples: A collection of samples for Fusion library: https://github.com/servicetitan/Stl.Fusion

ํ“จ์ „์— ๋Œ€ํ•ด ๋Œ€๋žต์ ์œผ๋กœ ์‚ดํŽด๋ด…์‹œ๋‹ค.

์ผ๋‹จ Overview๋งŒ ๋ด๋„ ๋จธ๋ฆฌ์— ์ฅ๊ฐ€ ๋‚˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ € ๊ฐœ๋…์ ์ธ ์ ‘๊ทผ์„ ํ•ด๋ด…๋‹ˆ๋‹ค.

Fusion์€ "Real-time User Interface"๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์ด๋Ÿฐ ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด์„  ์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ๋ณต์žกํ•ด์ง€๊ณ , ์ž‘์—…๋Ÿ‰์ด ๋งŽ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€๋งˆ๋‹ค ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ํ•  ๋•Œ ์ˆ™๋ จ๋œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ์ผ๊ด€๋˜๊ฒŒ ์ž‘์„ฑํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๊ณ , ๊ทธ ์•ˆ์—์„œ ๋‹ค์–‘ํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ จํ•ด์„œ ํŽ˜์ด์Šค๋ถ์ด๋‚˜ ํŠธ์œ„ํ„ฐ ๋“ฑ์˜ ์„œ๋น„์Šค๋“ค์€ ์ด๋Ÿฐ ๊ณ ํ†ต์„ ์ด๋ฏธ ๊ฒฝํ—˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

Fusion์„ ์ด์šฉํ•˜๋ฉด "Real-time User Interface"๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฐœ๋ฐœ์ž๊ฐ€ ์บ์‹ฑ, ๋ฌดํšจํ™”, ์ตœ์ข… ์ผ๊ด€์„ฑ ๊ด€์ ์—์„œ ์ž‘์—…ํ•ด์•ผ ํ•  ์ƒ๋‹นํ•œ ์ฝ”๋”ฉ๋Ÿ‰์„ ์ค„์—ฌ์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. (์•„์ง ํŒŒ์•…์ด ๋œ ๋˜์–ด์„œ ๊ฐœ๋…์ ์œผ๋กœ ์ ‘๊ทผ)

๋ถ„์‚ฐ ์ปดํ“จํŒ… ์„œ๋น„์Šค, ํˆฌ๋ช… ์ถ”์ƒํ™”์˜ ํŠน์ง•์œผ๋กœ, SignalR ์—†์ด Fusion๋งŒ์œผ๋กœ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํˆฌ๋ช… ์ถ”์ƒํ™”๋กœ ์ธํ•ด, ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

Fusion์€ 2020๋…„ 10์›”๋ถ€ํ„ฐ ์‹œ์ž‘๋œ ์•„์ฃผ ๋”ฐ๋ˆํ•œ ํ”„๋กœ์ ํŠธ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ƒ˜ํ”Œ์ฝ”๋“œ๋„ record๋“ฑ๋„ ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉ๋˜์–ด ์žˆ์–ด์„œ C# 9์„ ๋ฐฐ์šฐ๋Š”๋ฐ ์ข‹์€ ์ƒ˜ํ”Œ์ด ๋ฉ๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

๋‹ค์Œ์˜ ์˜จ๋ผ์ธ ์ƒ˜ํ”Œ์„ ํ†ตํ•ด Fusion์— ๋Œ€ํ•ด ์ข€ ๋” ์ž˜ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Stl.Samples.Blazor (servicetitan.com)

Board Games (alexyakunin.com)

์ƒ˜ํ”Œ์—์„œ Blazor Server ๋ฐ Webassembly ๋ชจ๋“œ๋ฅผ ๋‘˜๋‹ค ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์ธ์ƒ๊นŠ์Šต๋‹ˆ๋‹ค. Fusion์„ ์ด์šฉํ•œ ์ฝ”๋“œ๊ฐ€ Blazor ๋ชจ๋“œ์™€ ์ƒ๊ด€์—†์ด ๊ฑฐ์˜ ๊ฐ™๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

์ผ๋‹จ ์ฝ”๋“œ ๊ฐ๊ฐ์„ ๋Š๋ผ๊ธฐ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ Blazor ์•ฑ์„ ๋งŒ๋“ค๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์ด ์•ฑ์€ ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์ƒ๊ธฐ๋Š” ํด๋ฆญ์„ ๊ณต์œ ํ•˜๋Š”๊ฒŒ ๋  ํ…๋ฐ์š”, ๋ฌผ๋ก  ์ƒ˜ํ”Œ์„ ๋ณด๊ณ  ๋”ฐ๋ผํ•  ๊ฒ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด์„œ ์ตํ˜€ ๋ณด์ฃ 

์ƒ˜ํ”Œ์˜ ์œ„์น˜๋Š”, ์œ„ git clone์œผ๋กœ ๋‚ด๋ ค๋ฐ›์€ ์ƒ˜ํ”Œ์˜ src/Blazor/HelloBlazorServer ์ž…๋‹ˆ๋‹ค

๋จผ์ € NuGet ์ฐธ์กฐ๋ฅผ ๋ด…์‹œ๋‹ค.

  • Stl.Fusion.Client์™€ Stl.Fusion.Server๋กœ ๊ฐ๊ฐ Blazor Webassembly์™€ Server ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ฌด๋ž˜๋„ Blazor Server๊ฐ€ ๊ตฌ์กฐ๊ฐ€ ๋‹จ์ˆœํ•  ๊ฒƒ์ด๋ฏ€๋กœ ์ด๊ฒƒ์œผ๋กœ ์ง„ํ–‰ํ•ด๋ด…์‹œ๋‹ค.

๋จผ์ € Blazor Server๋กœ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ดˆ ๊ธฐ๋ณธ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š” ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์›น๋ธŒ๋ผ์šฐ์ € ๋‘๊ฐœ๋กœ ๋„์›Œ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

image

์‹คํ–‰ํ•ด๋ณด๋ฉด ์™ผ์ชฝ๊ณผ ์˜ค๋ฅธ์ชฝ์˜ ์นด์šดํ„ฐ๋Š” ์„œ๋กœ ๊ณต์œ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹ค์‹œ๊ฐ„์„ฑ์„ ๊ฐ€์ง€๋ ค๋ฉด ์ดํ™”๋ฉด์œผ๋กœ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ด๋ณผ ๋•Œ,

์™ผ์ชฝ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋‹น์—ฐํžˆ ์™ผ์ชฝ์˜ ์นด์šดํŠธ๋Š” ์ฆ๊ฐ€ํ•  ํ…๋ฐ, ์˜ค๋ฅธ์ชฝ ์ฐฝ์œผ๋กœ ์ด ์ •๋ณด๋ฅผ ๋ณด๋‚ผ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฆ‰,

์™ผ์ชฝ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฆ„ โ†’ ์ž์‹ ์˜ ์นด์šดํŠธ๋ฅผ ์ฆ๊ฐ€ํ•จ โ†’ ์นด์šดํŠธ๊ฐ€ ์ฆ๊ฐ€ํ–ˆ์Œ์„ ๊ฐ™์€ ์„ธ์…˜์˜ ๊ตฌ์„ฑ์›์—๊ฒŒ ์ „ํŒŒํ•จ โ†’ ๊ฐ™์€ ์„ธ์…˜์˜ ๋‹ค๋ฅธ ๊ตฌ์„ฑ์›์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Œ์„ ์ธ์‹ํ•จ โ†’ ์ž์‹ ์˜ ์นด์šดํŠธ๋ฅผ ๋ณ€๊ฒฝ๋œ ๊ฐ’์œผ๋กœ ๋™๊ธฐํ™” ํ•จ

๋ญ ์ด๋Ÿฐ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ณ€ํ™”๋ฅผ ์ ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ์š”, Fusion์€ ์–ด๋–ป๊ฒŒ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์‚ดํŽด๋ด…์‹œ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

NuGet์„ ํ†ตํ•ด Stl.Fusion.Server๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ Startup.cs์— Fusion ๊ด€๋ จ ์„ค์ •๋“ค์„ ํ•ฉ๋‹ˆ๋‹ค.

        public void ConfigureServices(IServiceCollection services)
        {
...
            // Fusion services
            services.AddSingleton<IUpdateDelayer>(_ => new UpdateDelayer(0.5));
            services.AddFusion(fusion => {
                fusion.AddFusionTime(); // IFusionTime is one of built-in compute services you can use
            });
            // This method registers services marked with any of ServiceAttributeBase descendants, including:
            // [Service], [ComputeService], [CommandService], [RestEaseReplicaService], etc.
            services.UseAttributeScanner().AddServicesFrom(Assembly.GetExecutingAssembly());
...

๋‚˜๋จธ์ง€๋Š” ๋™์ผํ•œ ๊ฒƒ ๊ฐ™๊ตฐ์š”. ์—ฌ๊ธฐ์„œ IUpdateDelayer๋Š” ์ˆ˜์ •๋œ ๋‚ด์šฉ์„ ๋ช‡์ดˆ๋’ค์— ๋ฐ˜์˜ํ• ์ง€์— ๋Œ€ํ•œ ์‹ฑ๊ธ€ํ†ค ์„œ๋น„์Šค๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. (์ด๊ฒŒ ์–ด๋–ค ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ๋ฐ˜์˜๋˜๋Š”์ง€๋Š” ์•„์ง์€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค)
๊ทธ๋ฆฌ๊ณ  AddFusion()์„ ํ†ตํ•ด Fusion ๊ด€๋ จ ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š”๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ์ƒ˜ํ”Œ์—๋Š” FusionTime๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. (๊ฐฑ์‹ ๋œ ์‹œ๊ฐ ๊ธฐ์ค€์œผ๋กœ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.)

๊ทธ๋ฆฌ๊ณ  UseAttributeScanner()๋Š” Fusion์—์„œ ์‚ฌ์šฉํ•˜๋Š” Attribute๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ด๋Š”๋ฐ์š”, AddServicesFrom()์„ ํ†ตํ•ด ํ˜„์žฌ ์‹คํ–‰ ์–ด์…ˆ๋ธ”๋ฆฌ์—์„œ๋งŒ ์ฐพ๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

App.Razor๋‚˜ _Host.cshtml, ์‹ฌ์ง€์–ด MainLayout.razor๊นŒ์ง€ ๋‹ฌ๋ผ์ง€๋Š”๊ฑด ์—†์Šต๋‹ˆ๋‹ค.

๋จผ์ € Counter.razor๋ฅผ ์‚ดํŽด๋ณด์ฃ 

Computed ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด Component๋กœ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Fusion์—์„œ๋Š” ComputedStateComponent๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ์š”,

@inherits ComputedStateComponent<string>

Counter.razor๋ฅผ ๋” ๋ณด๊ธฐ ์ „์— CounterService.cs๋ฅผ ๋ด…์‹œ๋‹ค.

    [ComputeService]
    public class CounterService
    {
        private readonly object _lock = new object();
        private int _count;
        private DateTime _changeTime = DateTime.Now;

        [ComputeMethod]
        public virtual Task<(int, DateTime)> Get()
        {
            lock (_lock) {
                return Task.FromResult((_count, _changeTime));
            }
        }

        public Task Increment()
        {
            lock (_lock) {
                ++_count;
                _changeTime = DateTime.Now;
            }
            using (Computed.Invalidate())
                Get();
            return Task.CompletedTask;
        }
    }

๋‘์‚ฌ๋žŒ์ด ํ•ด๋‹น ํŽ˜์ด์ง€์— ์ ‘์†ํ–ˆ์„ ๋•Œ CounterService์˜ ์ธ์Šคํ„ด์Šค๋Š” ์ด ๋ช‡๊ฐœ์ผ๊นŒ์š”? (์•„์ง ์ €๋„ ์ •ํ™•ํžˆ๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ๊ฐœ๋…์ƒ์œผ๋กœ๋Š” ํ•˜๋‚˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค)

1๊ฐœ์˜ ์ข‹์•„์š”

ํ . ์ฝ”๋“œ๋ฅผ ํ•ด์„ํ•˜๊ธฐ ์œ„ํ•ด ์กฐ๊ธˆ ๋” ํ•™์Šต์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ ํŠœํ† ๋ฆฌ์–ผ์„ ๋”ฐ๋ผ๊ฐ€ ๋ด…์‹œ๋‹ค.

Part 0: NuGet packages
Part 1: Compute Services
Part 2: Computed Values and IComputed<T>
Part 3: State: IState<T> and Its flavors
Part 4: Replica Services
Part 5: Caching and Fusion on Server-Side Only
Part 6: Real-time UI in Blazor Apps
Part 7: Real-time UI in JS / React Apps
Part 8: Scaling Fusion Services
Part 9: CommandR

1๊ฐœ์˜ ์ข‹์•„์š”

Fusion์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ƒ˜ํ”Œ์„ ๋”ฐ๋ผ ํ•˜๋Š”๊ฒƒ ๋งŒ์œผ๋กœ๋Š” Fusion์„ ์ดํ•ดํ•˜๋Š”๋ฐ ๋ถ€์กฑํ•จ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ œ๊ณตํ•˜๋Š” ํŠœํ† ๋ฆฌ์–ผ์„ ๋”ฐ๋ผ๊ฐ€๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Fusion์ด ์ œ๊ณตํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์ค‘์—์„œ ๊ฐ€์žฅ ํ•ต์‹ฌ์ ์ธ ๊ธฐ๋Šฅ์€ Compute Service๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์บ์‹ฑ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ์บ์‹ฑ์€ ์›์ž๊ฐ€ ๋ฌดํšจํ™”(๋‹ค์‹œ ๊ณ„์‚ฐ๋˜์–ด์•ผ ํ• )๊ฐ€ ๋˜์—ˆ์„ ๋•Œ ์บ์‹ฑ ํŠธ๋ฆฌ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ์ „๋žต์€ ์ž‘์€ ๊ทœ๋ชจ์˜ ์„œ๋น„์Šค์—์„œ๋Š” ์ž˜ ๋™์ž‘ํ•˜์ง€๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŽ์•„์ง€๊ฑฐ๋‚˜ ๊ทœ๋ชจ๊ฐ€ ํฐ ์„œ๋น„์Šค์—์„œ๋Š” ๊ทธ๋ฆฌ ํšจ์œจ์ ์ด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Fusion์€ ๋ฐ˜๋Œ€์˜ ์ „๋žต์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ „์ฒด๋ฅผ ์บ์‹ฑํ•˜๋˜, ์›์ž์  ๋ฌดํšจํ™”๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„, ์ „์ฒด๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํฐ ๊ทœ๋ชจ์˜ ์„œ๋น„์Šค์—์„œ ํ•„์š”๋กœ ํ•˜๋Š” ์บ์‹ฑ์—์„œ ์œ ์šฉํ•œ๋ฐ, ์บ์‹ฑ ํŠธ๋ฆฌ ์•ˆ์—์„œ ๋ฌดํšจํ™”๊ฐ€ ๋นˆ๋ฒˆํžˆ ๋ฐœ์ƒํ•  ์—ฌ์ง€๊ฐ€ ์ƒ๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

Fusion์€ Console ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ๋„ ์บ์‹ฑ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธ ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

.NET 5 ์—์„œ ํ…Œ์ŠคํŠธ ํ–ˆ์Šต๋‹ˆ๋‹ค.

dotnet new console
Install-Package Stl.Fusion

์ฃผ์„์„ ํ’€๊ณ  ํ•˜๋ฉด์„œ ๋™์ž‘์„ ํ™•์ธํ•ด๋ณด์‹œ์ฃ !

using Microsoft.Extensions.DependencyInjection;
using Stl.Async;
using Stl.DependencyInjection;
using Stl.Fusion;
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading.Tasks;
using static System.Console;


var counters = CreateServices().GetRequiredService<CounterService>();
for (var i = 0; i < 5; i++)
    WriteLine(await counters.Get("a"));

counters.Increment("a");

for (var i = 0; i < 5; i++)
    WriteLine(await counters.Get("a"));

//var services = CreateServices();
//var counterSum = services.GetRequiredService<CounterSumService>();
//WriteLine(await counterSum.Sum("a", "b"));
//WriteLine(await counterSum.Sum("a", "b"));

//var services = CreateServices();
//var counterSum = services.GetRequiredService<CounterSumService>();
//WriteLine("Nothing is cached (yet):");
//WriteLine(await counterSum.Sum("a", "b"));
//WriteLine("Only GetAsync(a) and GetAsync(b) outputs are cached:");
//WriteLine(await counterSum.Sum("b", "a"));
//WriteLine("Everything is cached:");
//WriteLine(await counterSum.Sum("a", "b"));

//var services = CreateServices();
//var counters = services.GetRequiredService<CounterService>();
//var counterSum = services.GetRequiredService<CounterSumService>();
//WriteLine(await counterSum.Sum("a", "b"));
//counters.Increment("a");
//WriteLine(await counterSum.Sum("a", "b"));

static IServiceProvider CreateServices()
{
    var services = new ServiceCollection();
    services.AddFusion();
    services.UseAttributeScanner().AddServicesFrom(Assembly.GetExecutingAssembly());
    return services.BuildServiceProvider();
}

[ComputeService]
public class CounterService
{
    private readonly ConcurrentDictionary<string, int> counters = new();

    [ComputeMethod]
    public virtual async Task<int> Get(string key)
    {
        WriteLine($"{nameof(Get)}({key})");

        await Task.Delay(500);

        return counters.TryGetValue(key, out var value) ? value : 0;
    }

    public void Increment(string key)
    {
        WriteLine($"{nameof(Increment)}({key})");
        counters.AddOrUpdate(key, k => 1, (k, v) => v + 1);
        using (Computed.Invalidate())
        {
            Get(key).Ignore();
        }
    }
}

[ComputeService]
public class CounterSumService
{
    public CounterService Counters { get; }

    public CounterSumService(CounterService counters) => Counters = counters;

    [ComputeMethod]
    public virtual async Task<int> Sum(string key1, string key2)
    {
        WriteLine($"{nameof(Sum)}({key1}, {key2})");
        return await Counters.Get(key1) + await Counters.Get(key2);
    }
}
1๊ฐœ์˜ ์ข‹์•„์š”

์œ„์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

  • ComputeServiceAttribute์— ์˜ํ•ด CounterServer๋Š” Compute Service๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • ComputeMethodAttribute์— ์˜ํ•ด Get() ๋ฉ”์†Œ๋“œ๋Š” Compute Method๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • Fusion์— ์˜ํ•ด Compute Service์™€ Method๋Š” ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • Compute Method๋Š” virtual async Task<TResult> MethodName(params1, params2, ...) ํ˜•ํƒœ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • Fusion์— ์˜ํ•ด ์ด ๋ฉ”์†Œ๋“œ๋Š” ์ž๋™์œผ๋กœ ์žฌ์ •์˜๋˜์–ด IComputed<TResult> ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋˜๊ณ , ์ด๊ฒƒ์„ ํ†ตํ•ด ์บ์‹ฑ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    • ์ด ๋•Œ, ์บ์‹ฑ ๊ธฐ์ค€(Key)์€ ๋ฉ”์†Œ๋“œ์ •๋ณด, ์ธ์Šคํ„ด์Šค, ์ธ์ž๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
    • Fusion์ด ์ž๋™์œผ๋กœ ์žฌ์ •์˜ ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ virtual ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Get() ๋ฉ”์†Œ๋“œ๋Š” ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ˆ˜ํ–‰์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ•˜๊ธฐ ์œ„ํ•ด Task.Delay(500)์œผ๋กœ 0.5์ดˆ ๋”œ๋ ˆ์ด๋ฅผ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Get(a)
0
0
0
0
0
Increment(a)
Get(a)
1
1
1
1
1

์ด๊ฒƒ์˜ ์˜๋ฏธ๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? ์ตœ์ดˆ ๊ฐ’์ด ๊ณ„์‚ฐ๋œ ํ›„ ์ดํ›„ ์บ์‹ฑ๋œ ๊ฐ’์„ ๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (0.5์ดˆ์˜ ์ฒ˜๋ฆฌ ์†Œ์š”์‹œ๊ฐ„์ด ์บ์‹ฑ์„ ํ†ตํ•ด ์—†์–ด์กŒ์Šต๋‹ˆ๋‹ค!)

๊ทธ๋Ÿฐ๋ฐ, ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•  ๊ฐ’์€ Increment()๋ฉ”์†Œ๋“œ์— ์˜ํ•ด ์˜ํ–ฅ์„ ๋ฐ›๊ฒŒ ๋˜๋Š”๋ฐ์š”, Fusion์—์„œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ๋ฌดํšจํ™” ์ฝ”๋“œ๋ฅผ using (Computed.Invalidate()) { ... } ๋ธ”๋Ÿญ ์•ˆ์—์„œ ํ•œ๋ฒˆ์€ ํ˜ธ์ถœํ•˜๋Š”๊ฒƒ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (Ignore()๋Š” ์ปดํŒŒ์ผ ๊ฒฝ๊ณ ๋ฅผ ์—†์• ๊ธฐ ์œ„ํ•œ Fusion์—์„œ ์ œ๊ณตํ•˜๋Š” ์ผ์ข…์˜ ๋ฐฉํŽธ์ž…๋‹ˆ๋‹ค.)
Fusion์€ ์ด ๋ธ”๋Ÿญ(ComputeContextScope)์˜ Compute Method๋ฅผ ๋ฌดํšจํ™” ํ•ฉ๋‹ˆ๋‹ค.

๋‘๋ฒˆ์งธ Compute Service์ธ CounterSumService๋ฅผ ๋ณผ๊นŒ์š”?

Fusion์€ DependencyInjection์„ ํ†ตํ•ด ์ƒ์„ฑ์ธ์ž๋กœ ๋‹ค๋ฅธ Compute Service ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด ์ฆˆ์Œ๋ถ€ํ„ฐ Compute Service๋Š” ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค์ž„์ด ์ž๋ช…ํ•ด์ง‘๋‹ˆ๋‹ค)
๊ทธ๋ฆฌ๊ณ  ComputeAttribute๋ฅผ ํ†ตํ•ด Compute Method ์กฐํ•ฉ์œผ๋กœ ์ƒˆ๋กœ์šด Compute Method๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ๋””์ž์ธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (์ด๋•Œ์—๋„ ๋™์ผํ•œ ์บ์‹ฑ ๊ธฐ์ค€(Key)๋Š” ๋ฉ”์†Œ๋“œ์ •๋ณด, ์ธ์Šคํ„ด์Šค, ์ธ์ž๊ฐ€ ๋ฉ๋‹ˆ๋‹ค)

์ฆ‰, ์ธ์ž (โ€œaโ€, โ€œbโ€)์™€ (โ€œbโ€, โ€œaโ€)๋Š” ๋‹ค๋ฅธ ์บ์‹ฑ ๋Œ€์ƒ์ด ๋ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ ์ฃผ์„์„ ํ’€๊ณ  ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด๋ณผ๊นŒ์š”?

Nothing is cached (yet):
Sum(a, b)
Get(a)
Get(b)
0
Only GetAsync(a) and GetAsync(b) outputs are cached:
Sum(b, a)
0
Everything is cached:
0

์‹คํ–‰ํ•ด๋ณด์‹œ๊ณ  ๊ทธ ์˜๋ฏธ๊ฐ€ ์ดํ•ด๋œ๋‹ค๋ฉด, Fusion์—์„œ ์ œ๊ณตํ•˜๋Š” ์บ์‹ฑ ๊ธฐ๋Šฅ์˜ ๋ง‰๊ฐ•ํ•จ์„ ๋Š๋‚„ ์ˆ˜ ์žˆ๊ณ  Service-Client ๋ชจ๋ธ์— ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ์‘์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋Š๋ผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ง€ ์บ์‹ฑ๋ถ€๋ถ„๋งŒ ๋ถ„์„ํ–ˆ์„ ๋ฟ์ธ๋ฐ, ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ๊ตต์งํ•œ ๊ธฐ๋Šฅ๊นŒ์ง€ ํ•ด์„œ 2020๋…„10์›”๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ ํ”„๋กœ์ ํŠธ๋ผ๋Š”๊ฒŒ ๋ฏฟ๊ฒจ์ง€์ง€๊ฐ€ ์•Š๋Š”๊ตฐ์š”!

2๊ฐœ์˜ ์ข‹์•„์š”

ํŠœํ† ๋ฆฌ์–ผ์— ์ข€ ๋” ์ƒ์„ธํ•œ ๋‚ด์šฉ์ด ์žˆ์œผ๋‹ˆ Compute Services ๋ฐ Computed Values and IComputed<T>๋ฅผ ์ž์„ธํžˆ ๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋‹ค์Œ์œผ๋กœ State์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํŠœํ† ๋ฆฌ์–ผ์— ์˜ํ•ด Fusion์˜ ๋‘๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…์— ๋Œ€ํ•ด ์ด์ œ ์•Œ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ์š”,

  • Compute Service์™€ ์„œ๋น„์Šค์˜ ์กฐํ•ฉ์„ ํ†ตํ•ด ํ•œ๋ฒˆ๋งŒ ๊ณ„์‚ฐํ•˜๋ฉด ์กฐํ•ฉ๋œ ๊ฐ’ ์ค‘์˜ ํ•˜๋‚˜๊ฐ€ ๋ฌดํšจํ™”๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ๊ณ„์† ์บ์‹ฑ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๊ณ ์š”,
  • ์ด๋Ÿฌํ•œ ๋ชจ๋“  ์ข…์†์„ฑ์„ ์ถ”์ฒ™ํ•˜๋Š” Fusion์ด ๊ตฌํ˜„ํ•˜๋Š” IComputed ์— ๋Œ€ํ•ด์„œ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ IState<T>์— ๋Œ€ํ•ด ์•Œ ํ•„์š”๊ฐ€ ์žˆ๋Š”๋ฐ์š”, IState<T>๋Š” ์ฆ‰ โ€œ์ƒํƒœโ€ ์ž…๋‹ˆ๋‹ค.

Fusion์—์„œ ๊ณ„์‚ฐ๋œ ๊ฐ’์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ณ„์‚ฐ๋˜์—ˆ์œผ๋ฏ€๋กœ ๋ฌดํšจํ™” ๋  ๋•Œ๊นŒ์ง€ "๋ถˆ๋ณ€์„ฑ"์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

IState<T>๋Š” ์ตœ์‹  ๋ฒ„์ ผ์„ โ€œ์ถ”์ โ€ ํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์œ ํ˜•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • IIMutableState
  • IComputedState
  • ILiveState
  • ILiveState<T, TLocals>
2๊ฐœ์˜ ์ข‹์•„์š”
1๊ฐœ์˜ ์ข‹์•„์š”

์•„๋งˆ๋„ Stl.Fusion์˜ ์‚ฌ์šฉ์ž๊ฐ€ ํ•œ๊ตญ์— ์ € ๋ฐ–์— ์—†์„ ์ˆ˜๋„ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค ^^;

Stl.Fusion์€ ์ดํ•ดํ•˜๊ณ  ์“ฐ๊ธฐ์—๋Š” ๋Ÿฌ๋‹์ปค๋ธŒ๊ฐ€ ์žˆ๋Š” ์‹ค์‹œ๊ฐ„ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ๋‚˜ ๋งŒ๋“œ์‹  ๋ถ„์˜ ๊ฐ€์ด๋“œ ๋ฌธ์„œ๊ฐ€ ๋Œ€๋žต ์ดํ•ด์‹œํ‚ค๊ณ  ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๋„๋ก์˜ ๊ด€์ ์ด ์•„๋‹ˆ๋ผ ๊นŠ๊ฒŒ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฌธ์„œ๋ผ ์‹ค์‹œ๊ฐ„ ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณธ ๊ฒฝํ—˜์ด ์—†์„ ๊ฒฝ์šฐ ๋˜๋ ค ์ƒ๋‹นํžˆ ๋‚œํ•ดํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ Stl.Fusion์„ ์‚ฌ์šฉํ•ด์„œ ASP.NET Core Web API + ์œˆํผ์œผ๋กœ ์ฑ„ํŒ… ํ”„๋กœ๊ทธ๋žจ ๊ฐ„๋‹จํžˆ ๋งŒ๋“ค์–ด์„œ ์†Œ๊ฐœํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

3๊ฐœ์˜ ์ข‹์•„์š”

image

Stl.Fusion์œผ๋กœ ๊ฐ„๋‹จํžˆ ๋งŒ๋“  ํ“จ์ „ ์ฑ—

3๊ฐœ์˜ ์ข‹์•„์š”

๊ด€๋ จ ๋ธ”๋กœ๊ทธ๋Š” ๊ณง ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค;;

1๊ฐœ์˜ ์ข‹์•„์š”

๋‹ค์Œ์€ Uno๋ฅผ ์ด์šฉํ•ด ๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ์˜ ์•ฑ์œผ๋กœ (Webasembly ํฌํ•จ!) ์บ ํผ์Šค๋ฅผ ๊ณต์œ ํ•ด ๊ฐ™์ด ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ์˜ˆ์ œ๋ฅผ ์ง„ํ–‰ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์ข‹์•„์š”