<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>yogingang</title>
    <link>https://yogingang.tistory.com/</link>
    <description>c#, ASPNET Core, Blazor, Web API, Programming blog</description>
    <language>ko</language>
    <pubDate>Sun, 9 Aug 2026 09:49:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>yogingang</managingEditor>
    <item>
      <title>MySql 에서 schedule 처리 하기</title>
      <link>https://yogingang.tistory.com/482</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;특정 작업을 하다 보면 db 에 data 를 일정 시간마다 update 하는 경우가 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 back up 같은 경우가 그렇고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;daily 마다 reset 하는 table 들도 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우 mysql 은 event 를 이용해 처리 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 구문은 매 1분 마다 table 을 truncate 하는 구문이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713090143913&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE EVENT daily_truncate
ON SCHEDULE
    EVERY 1 MINUTE
    STARTS CURRENT_TIMESTAMP
ON COMPLETION PRESERVE
DO
    BEGIN
        TRUNCATE TABLE your_table_name;
    END;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 mysql 은 rdbms 이고 관계형 db 는 필연적으로 foreign key 를 갖게된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 특정 table 을 비울때 오류가 날 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음과 같은 구문을 추가해서 처리 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(주의 : 이 구문은 자신이 비우려는 table 과 연계된 table 도 다시 지울 거라는 가정에 해야한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713090288200&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE EVENT daily_truncate
ON SCHEDULE
    EVERY 1 MINUTE
    STARTS CURRENT_TIMESTAMP
ON COMPLETION PRESERVE
DO
    BEGIN
    	SET FOREIGN_KEY_CHECKS = 0;
        TRUNCATE TABLE your_table_name;
        SET FOREIGN_KEY_CHECKS = 1;
    END;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;**외래 키 제약 조건을 해제할 경우 데이터 무결성이 보장되지 않을 수 있으므로, &lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;이러한 조치를 취하기 전에 주의깊게 검토해야 한다.**&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 이 구문을 ef core 에서 실행해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 우리는 quartz 를 이용해 특정시간 마다 insert 하는 app 을 작성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yogingang.tistory.com/481&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yogingang.tistory.com/481&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713090417827&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Quartz 를 이용한 schedule job 처리&quot; data-og-description=&quot;프로그래밍을 하다보면 일정시간에 정기적으로 실행되야 하는 process 가 존재한다. 이런 process 를 처리할때 여러가지 방법이 있다. 1. OS 에 특정 기능(scheduler, crontab 등등)을 이용한다. 2. language 의&quot; data-og-host=&quot;yogingang.tistory.com&quot; data-og-source-url=&quot;https://yogingang.tistory.com/481&quot; data-og-url=&quot;https://yogingang.tistory.com/481&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c90JF6/hyVPQmefe6/Cl6Jk9Kv9geFRCG5WFyJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kIRGQ/hyVMY0vlKS/Q4tAVMd04I3mydQvODklK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://yogingang.tistory.com/481&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yogingang.tistory.com/481&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c90JF6/hyVPQmefe6/Cl6Jk9Kv9geFRCG5WFyJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kIRGQ/hyVMY0vlKS/Q4tAVMd04I3mydQvODklK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Quartz 를 이용한 schedule job 처리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로그래밍을 하다보면 일정시간에 정기적으로 실행되야 하는 process 가 존재한다. 이런 process 를 처리할때 여러가지 방법이 있다. 1. OS 에 특정 기능(scheduler, crontab 등등)을 이용한다. 2. language 의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yogingang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 app 에&amp;nbsp; mysql 의&amp;nbsp; event 를 이용해 일정시간마다 truncate table 하는 scheduler 를 작성해 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EF Core 에서 직접 sql 구문을 실행 하게 하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql 에 이미 event 가 있는지 확인 한 후 없으면 event 를 생성하게 하자&lt;/p&gt;
&lt;pre id=&quot;code_1713092332397&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//일단 event 생성 구문 부터 if not exist 를 적용해 보자
CREATE EVENT IF NOT EXISTS daily_truncate
ON SCHEDULE
    EVERY 1 MINUTE
    STARTS CURRENT_TIMESTAMP
ON COMPLETION PRESERVE
DO
    BEGIN
    	SET FOREIGN_KEY_CHECKS = 0;
        TRUNCATE TABLE your_table_name;
        SET FOREIGN_KEY_CHECKS = 1;
    END;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 구문을 ef core 에서 실행해 보자&lt;/p&gt;
&lt;pre id=&quot;code_1713093252157&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var sql = @&quot;
    USE `efcore-test`;
    CREATE EVENT IF NOT EXISTS daily_truncate
    ON SCHEDULE
        EVERY 1 MINUTE
        STARTS CURRENT_TIMESTAMP
    ON COMPLETION PRESERVE
    DO
    BEGIN
        TRUNCATE TABLE Users;
    END;
&quot;;

await Database.ExecuteSqlRawAsync(sql);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 만든후 실행 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;web api 같은 경우 program.cs 에서 app run 이전에 실행하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713093312563&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await RegisterMySqlEventScheduler(app);

app.Run();


static async Task RegisterMySqlEventScheduler(WebApplication app)
{
    using var scope = app.Services.CreateScope();
    
    var dbContext = scope.ServiceProvider.GetRequiredService&amp;lt;BaseDbContext&amp;gt;();
    await dbContext.CreateEventAsync();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 10초에 한번씩 data 가 생성되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1분에 한번씩 data 를 삭제할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/mbXB2GCytNQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/mbXB2GCytNQ&lt;/a&gt;&lt;/p&gt;</description>
      <category>ASPNET/WebApi</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/482</guid>
      <comments>https://yogingang.tistory.com/482#entry482comment</comments>
      <pubDate>Mon, 15 Apr 2024 00:00:38 +0900</pubDate>
    </item>
    <item>
      <title>Quartz 를 이용한 schedule job 처리</title>
      <link>https://yogingang.tistory.com/481</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍을 하다보면 일정시간에 정기적으로 실행되야 하는 process 가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 process 를 처리할때 여러가지 방법이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. OS 에 특정 기능(scheduler, crontab 등등)을 이용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. language 의 도움을 받아 timer 로 구현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 3rd&amp;nbsp; party library 의 도움을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이 세가지 중에 3 번을 이용해 보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quartz.net 이라고 dotnet 용 scheduler library(framework) 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 web api 의 간단한 project 를 만들고 project folder 로 이동하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quartz 를 설치하자&lt;/p&gt;
&lt;pre id=&quot;code_1712467291023&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package quartz.aspnetcore&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user 를 생성하는 api 가 존재 했으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 api 를 10초 간격으로 호출 하도록 해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저&amp;nbsp; job 을 하나 만들자&lt;/p&gt;
&lt;pre id=&quot;code_1712470738891&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[DisallowConcurrentExecution] // job 실행중에 schedule delay 가 되어 동시에 실행되지 않도록 방지
public class InsertUserJob(IMessageBus bus) : IJob
{
    private readonly IMessageBus _bus = bus;

    public async Task Execute(IJobExecutionContext context)
    {
        var id  = Guid.NewGuid().ToString();

        var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
        byte[] intByte = new byte[4];
        rng.GetBytes(intByte);
        int age = Math.Abs(BitConverter.ToInt32(intByte, 0));
       
        var command = new Add.Command
        {
             Name = id,
             Email = $&quot;{id}@email.net&quot;,
             Age = age

        };
        var response = await _bus.InvokeAsync&amp;lt;Add.Response&amp;gt;(command);
        Console.WriteLine(response.Result);
        Console.WriteLine(response.Error?.Message);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 job 을 실행하기 위한 Quartz config 을 설정하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712470786164&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.AddQuartz(q =&amp;gt;
{
    q.SchedulerId = &quot;Quartz-Scheduler-Core&quot;;
    q.UseMicrosoftDependencyInjectionJobFactory();
    q.UseSimpleTypeLoader();
    q.UseInMemoryStore();
    q.UseDefaultThreadPool(p =&amp;gt;
    {
        p.MaxConcurrency = 10;
    });

    #region Insert User
    var jobKey = new JobKey(&quot;InsertUser&quot;, nameof(InsertUserJob));
    q.AddJob&amp;lt;InsertUserJob&amp;gt;(jobKey);

    q.AddTrigger(
     cfg =&amp;gt; cfg.ForJob(jobKey)
    .WithIdentity(&quot;InsertUser-Trigger&quot;)
    .WithSimpleSchedule(x =&amp;gt; x.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever())
    );
    #endregion 

});

builder.Services.AddQuartzHostedService(options =&amp;gt;
{
    options.WaitForJobsToComplete = true;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 하면 10초에 한번씩 우리가 만든 job 을 실행하게 될것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만든 job 은 그안에서 직접 무언가를 처리해도 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;command 를 호출해서 이미 만들어 놓은 다른 process 를 처리 해도 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자를 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/Po88w7jQ6WQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/Po88w7jQ6WQ&lt;/a&gt;&lt;/p&gt;</description>
      <category>ASPNET/WebApi</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/481</guid>
      <comments>https://yogingang.tistory.com/481#entry481comment</comments>
      <pubDate>Mon, 8 Apr 2024 00:00:23 +0900</pubDate>
    </item>
    <item>
      <title>AWS DynamoDB 를 이용한 동시성 충돌 처리</title>
      <link>https://yogingang.tistory.com/480</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;aws DynamoDB 도 동시성 충돌 문제가 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 사용자가 사용하기 때문에 동시에 특정 data 에 접근하게 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 동시성 충돌에 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그 문제를 해결 하는 방법을 알아보자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 강좌에서 data 를 저장하는 방법을 알아봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yogingang.tistory.com/479&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yogingang.tistory.com/479&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711867816094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AWS DynamoDB 를 이용한 data 저장&quot; data-og-description=&quot;AWS 에는 KeyValue 기반의 NoSQL Data Service 가 있다. DynamoDB 라고 한다. .NET 에서 AWS DynamoDB 를 사용하여 Table 을 생성하고 CRUD 하는 간단한 app 을 만들어 보겠다. DynamoDB 를 이용하여 Programming 하는 방법에&quot; data-og-host=&quot;yogingang.tistory.com&quot; data-og-source-url=&quot;https://yogingang.tistory.com/479&quot; data-og-url=&quot;https://yogingang.tistory.com/479&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WdKoj/hyVGKAyPaB/adnaKKUtVQUJ3hWk6AIoo1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dfs7rY/hyVGK1DC32/TwYfMrTFf76cZ1iNAvLeBK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://yogingang.tistory.com/479&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yogingang.tistory.com/479&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WdKoj/hyVGKAyPaB/adnaKKUtVQUJ3hWk6AIoo1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dfs7rY/hyVGK1DC32/TwYfMrTFf76cZ1iNAvLeBK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AWS DynamoDB 를 이용한 data 저장&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AWS 에는 KeyValue 기반의 NoSQL Data Service 가 있다. DynamoDB 라고 한다. .NET 에서 AWS DynamoDB 를 사용하여 Table 을 생성하고 CRUD 하는 간단한 app 을 만들어 보겠다. DynamoDB 를 이용하여 Programming 하는 방법에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yogingang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 model 을 기준으로 작업해 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User 모델을 조금 수정할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711867894845&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[DynamoDBTable(&quot;User&quot;)]
public class User
{
    [DynamoDBHashKey] // Primary Key
    public Guid Id { get; set; } = Guid.NewGuid();

    [DynamoDBProperty]
    public string Name { get; set; }

    [DynamoDBProperty]
    public string Password { get; set; }

    [DynamoDBProperty]
    public string Email { get; set; }

    [DynamoDBProperty]
    public int Age { get; set; }
    [DynamoDBVersion] // attribute 를 추가해주자
    public int? Version { get; set; } //&amp;lt;-- 충돌 check 를 위해 version 이 추가 되었다. 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711867936168&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public class CommandHandler(IMapper mapper, IDynamoDBContext dbContext)
 {
     private readonly IMapper _mapper = mapper;
     private readonly IDynamoDBContext _dbContext = dbContext;

     public async Task&amp;lt;Response&amp;gt; Handle(Command command)
     {
         while (true)
         {
             var user = await _dbContext.LoadAsync&amp;lt;Infrastructure.Models.Dynamo.User&amp;gt;(command.Id);
             user.Name = $&quot;Concurrency Conflict Resolved {DateTime.Now}&quot;;
             //var user = _mapper.Map&amp;lt;Infrastructure.Models.Dynamo.User&amp;gt;(command);
             try
             {
                 await _dbContext.SaveAsync(user);
                 return new Response { Result = true };
             }
             catch (ConditionalCheckFailedException ex)
             {
                 Console.WriteLine(ex.ToString());
             }
         }
     }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handler 를 확인 하면 try catch 구문에서 ConditionalCheckFailedException 이 발생하는 것을 알수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 발생하면 간단한 logging 정도 하고 다시 LoadAsync 를 실행하여 최신 data 를 가져온 후 적용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/GvjVvbWFtCw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/GvjVvbWFtCw&lt;/a&gt;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/480</guid>
      <comments>https://yogingang.tistory.com/480#entry480comment</comments>
      <pubDate>Mon, 1 Apr 2024 00:00:16 +0900</pubDate>
    </item>
    <item>
      <title>AWS DynamoDB 를 이용한 data 저장</title>
      <link>https://yogingang.tistory.com/479</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 에는 KeyValue 기반의 NoSQL Data Service 가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DynamoDB 라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.NET 에서 AWS DynamoDB 를 사용하여 Table 을 생성하고 CRUD 하는 간단한 app 을 만들어 보겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DynamoDB 를 이용하여 Programming 하는 방법에는 여러가지가 있는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.NET 에는 &lt;b&gt;Document model&lt;/b&gt; 과 &lt;b&gt;Object persistence model&lt;/b&gt; 이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Document Model&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;장점&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유연한 데이터 구조를 지원하며, JSON과 유사한 형식의 데이터를 저장할 수 있다.&lt;/li&gt;
&lt;li&gt;풍부한 쿼리 기능을 제공하며, DynamoDB의 강력한 쿼리 및 검색 기능을 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;단점&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좀더 많은 작업이 필요하고 좀더 불편하다.&lt;/li&gt;
&lt;li&gt;데이터 일관성을 관리하는 데 어려움이 있을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Object Persistence Model&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 매핑: Object Persistence 모델은 .NET 객체와 DynamoDB 아이템 간의 매핑을 제공한다. &lt;br /&gt;이는 개발자가 객체 지향적인 방식으로 데이터를 다룰 수 있게 해준다.&lt;/li&gt;
&lt;li&gt;단순성: 객체 모델은 간단하고 직관적인 API를 제공하여 개발자가 빠르게 개발할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제한된 유연성: Object Persistence 모델은 .NET 객체와 DynamoDB 아이템 간의 일대일 매핑을 가정하기 때문에, 데이터 구조의 유연성이 제한될 수 있다.&lt;/li&gt;
&lt;li&gt;성능: 매핑 레이어를 통해 객체와 DynamoDB 아이템 간의 변환은 추가적인 오버헤드를 초래할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Table 을 하나 생성해 보자&lt;/p&gt;
&lt;pre id=&quot;code_1711280816245&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package AWSSDK.DynamoDBv2
dotnet add package AWSSDK.Extensions.NETCore.Setup&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aspnet core 에 설치해서 aws configure 에 설정된 권한을 사용할 것이므로 위와 같이 두 개의 package 를 설치 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711280886940&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
// AWS DynamoDB 클라이언트 구성
builder.Services.AddAWSService&amp;lt;IAmazonDynamoDB&amp;gt;();
builder.Services.AddTransient&amp;lt;IDynamoDBContext, DynamoDBContext&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711280934380&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CommandHandler(IMapper mapper, IAmazonDynamoDB dynamoDBClient)
{
    private readonly IMapper _mapper = mapper;
    private readonly IAmazonDynamoDB _dynamoDBClient = dynamoDBClient;

    public async Task&amp;lt;Response&amp;gt; Handle(Command command)
    {
        var request = new CreateTableRequest
        {
            TableName = &quot;User&quot;,
            AttributeDefinitions = new List&amp;lt;AttributeDefinition&amp;gt;
            {
                new AttributeDefinition
                {
                    AttributeName = &quot;Id&quot;,
                    AttributeType = ScalarAttributeType.S
                }
                // 다른 속성 정의도 추가할 수 있음
            },
            KeySchema = new List&amp;lt;KeySchemaElement&amp;gt;
            {
                new KeySchemaElement
                {
                    AttributeName = &quot;Id&quot;,
                    KeyType = KeyType.HASH
                }
            },
            ProvisionedThroughput = new ProvisionedThroughput
            {
                ReadCapacityUnits = 5,
                WriteCapacityUnits = 5
            }
        };

        var response = await _dynamoDBClient.CreateTableAsync(request);
        //Console.WriteLine(&quot;Table creation response: &quot; + response);
        return await Task.FromResult(new Response { Result = true });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Table 이 만들어 졌으면 Add 해보자. &lt;b&gt;Object Persistence Model&lt;/b&gt; 을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711281012699&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[DynamoDBTable(&quot;User&quot;)]
public class User
{
    [DynamoDBHashKey] // Primary Key
    public Guid Id { get; set; } = Guid.NewGuid();

    [DynamoDBProperty]
    public string Name { get; set; }

    [DynamoDBProperty]
    public string Password { get; set; }

    [DynamoDBProperty]
    public string Email { get; set; }

    [DynamoDBProperty]
    public int Age { get; set; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711281043605&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public class CommandHandler(IMapper mapper, IDynamoDBContext dbContext)
 {
     private readonly IMapper _mapper = mapper;
     private readonly IDynamoDBContext _dbContext = dbContext;

     public async Task&amp;lt;Response&amp;gt; Handle(Command command)
     {
         var user = _mapper.Map&amp;lt;Infrastructure.Models.Dynamo.User&amp;gt;(command);
         await _dbContext.SaveAsync(user);
         return new Response { Result = true };
     }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/zuEqpXoqA98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/zuEqpXoqA98&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/479</guid>
      <comments>https://yogingang.tistory.com/479#entry479comment</comments>
      <pubDate>Mon, 25 Mar 2024 00:00:48 +0900</pubDate>
    </item>
    <item>
      <title>대용량 데이터 처리시 발생 하는 동시성 충돌 처리</title>
      <link>https://yogingang.tistory.com/478</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB 와 관련된 Transaction 처리를 하다 보면 &lt;b&gt;동시성 충돌&lt;/b&gt; 이라는 문제를 접하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 동시성 충돌이 무엇이고 EF Core 에서 어떻게 해결해야 하는지 알아보겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 동시성 충돌은 동시에 여러 트랜잭션이 같은 데이터나 자원을 변경하려고 할 때 발생하는 문제이다. 이는 데이터베이스의 일관성과 무결성을 해치는 문제로, 데이터베이스 시스템에서 주의해야 할 중요한 측면 중 하나이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시성 충돌은 주로 다음과 같은 상황에서 발생할 수 있다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;동시성 제어 문제&lt;/b&gt;: 여러 트랜잭션이 동시에 데이터를 읽거나 쓰는 경우 발생할 수 있다. 이때 한 트랜잭션이 데이터를 변경하고 있는 동안 다른 트랜잭션이 동일한 데이터를 변경하려고 하면 충돌이 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;교착 상태(Deadlock)&lt;/b&gt;: 두 개 이상의 트랜잭션이 서로가 소유한 자원을 대기하고 있어 무한 대기 상태에 빠지는 현상이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽기-쓰기 충돌&lt;/b&gt;: 한 트랜잭션이 데이터를 읽으려고 할 때 다른 트랜잭션이 이미 그 데이터를 변경하고 있는 경우 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쓰기-쓰기 충돌&lt;/b&gt;: 두 개 이상의 트랜잭션이 동일한 데이터를 변경하려고 할 때 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 충돌을 해결하기 위해서는 데이터베이스 관리 시스템(DBMS)이 적절한 동시성 제어 기법을 사용해야 한다. 이를 통해 트랜잭션 간의 충돌을 감지하고 관리하여 데이터 일관성을 유지할 수 있다. 주요한 동시성 제어 기법으로는 Locking, MVCC(Multi-Version Concurrency Control), Snapshot Isolation 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EF Core는 동시성 충돌이 비교적 드물다고 가정하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;낙관적 동시성을 구현한다. 데이터를 미리 잠그고 수정을 진행하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;비관적&lt;span&gt;&amp;nbsp;&lt;/span&gt;접근 방식과 달리 낙관적 동시성은 잠금을 사용하지 않지만 쿼리된 이후 데이터가 변경된 경우 저장 시 데이터 수정이 실패하도록 준비한다. 이 동시성 오류는 새 데이터에 대한 전체 작업을 다시 시도하여 해당 오류를 처리하는 애플리케이션에 보고된다. EF Core에서 낙관적 동시성은 속성을&lt;span&gt;&amp;nbsp;&lt;/span&gt;동시성 토큰으로 구성하여 구현된다. 동시성 토큰은 다른 속성과 마찬가지로 엔터티를 쿼리할 때 로드되고 추적된다. 그런 다음&lt;span&gt;&amp;nbsp;&lt;/span&gt;SaveChanges() 중에 업데이트 또는 삭제 작업이 수행되면 데이터베이스의 동시성 토큰 값은 EF Core에서 읽은 원래 값과 비교된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 Modle 을 정의해 보자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710672109346&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class User
{
    [Key]
    public Guid Id { get; set; } = Guid.NewGuid();
    public string Name { get; set; }
    public string Password { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
    [Timestamp] // concurrency check 을 위해 속성설정
    public byte[] Version { get; set; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서 Version 은 SqlServer 또는 MySql 에서 동시성 충돌을 감지할 때 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 동시성 충돌을 일으켜 보자&lt;/p&gt;
&lt;pre id=&quot;code_1710672260624&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    var user = _dbContext.Users.Where(u =&amp;gt; u.Email == command.Email).FirstOrDefault();
    if (user == null) throw new Exception(&quot;Could not find user&quot;);

    user.Age  = user.Age &amp;gt; 0 ? user.Age -1 : throw new Exception(&quot;Age is == 0&quot;);
    _dbContext.Database.ExecuteSqlRaw($&quot;UPDATE `efcore-test`.Users SET Age = Age-1 WHERE Email = 'test2' and Age &amp;gt; 0 and Id='{user.Id}'&quot;);

    ...
    _dbContext.Users.Update(user);
    _dbContext.SaveChanges();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user 를 찾아서 age 를 update 하기 전에&amp;nbsp; ExecuteSqlRaw 를 이용해서 age 를 외부에서 Update 한 효과를 내자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 test2 의 age 의 값이 가져온 값과 달라지게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update 후 savechanges 하면 이제 DbUpdateConcurrencyException 이 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 문제를 해결해 보자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710672415636&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while(true)
{
     try
     {
         _dbContext.Users.Update(user);
         _dbContext.SaveChanges();
         break;
     }
     catch (DbUpdateConcurrencyException ex)
     {


         foreach (var entry in ex.Entries)
         {
             if (entry.Entity is Infrastructure.Models.User)
             {
                 var proposedValues = entry.CurrentValues;
                 var databaseValues = entry.GetDatabaseValues();

                 foreach (var property in proposedValues.Properties)
                 {
                     var proposedValue = proposedValues[property];
                     var databaseValue = databaseValues[property];

                     // TODO: decide which value should be written to database
                     if (property.Name.Equals(&quot;Age&quot;, StringComparison.OrdinalIgnoreCase))
                         proposedValues[property] = (proposedValues[property] as int?) &amp;gt; 0 ? (proposedValues[property] as int?) - 1 : throw new Exception(&quot;Age is == 0&quot;);

                 }

                 // Refresh original values to bypass next concurrency check
                 entry.OriginalValues.SetValues(databaseValues);
             }
             else
             {
                 throw new NotSupportedException(
                     &quot;Don't know how to handle concurrency conflicts for &quot;
                     + entry.Metadata.Name);
             }
         }
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정하게 되면 동시성 충돌이 일어났을때 다시 최신 db 값을 가져와서 실행하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 가져온 값이 (여기서는 age) 0 보다 작거나 같으면 error 가 발생하고 더이상 진행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/Fbyaz8WXowo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/Fbyaz8WXowo&lt;/a&gt;&lt;/p&gt;</description>
      <category>EntityFramewok Core (EF Core)</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/478</guid>
      <comments>https://yogingang.tistory.com/478#entry478comment</comments>
      <pubDate>Mon, 18 Mar 2024 00:00:24 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 를 이용한 실시간 데이터 스트리밍</title>
      <link>https://yogingang.tistory.com/477</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;Kafka&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실시간으로 기록 스트림을 게시, 구독, 저장 및 처리할 수 있는 분산형&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0066cc;&quot; href=&quot;https://www.redhat.com/ko/topics/integration/what-is-streaming-data&quot;&gt;데이터 스트리밍&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플랫폼이다. 하루에 1조 4천억 건의 메시지를 처리하기 위해 LinkedIn이 개발한 내부 시스템으로 시작했으나, 현재 이는 다양한 기업의 요구 사항을 지원하는 애플리케이션을 갖춘 오픈소스 데이터 스트리밍 솔루션이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 요즘 같은 micro service (??) 환경 또는 대규모 데이터 처리가 필요한 상황에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간으로 data 를 주고 받는 처리를 일반적인 tcp/ip 기반의 app 을 개발하여 처리하기에는 한계가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같은 대용량에 특화된 솔루션을 이용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;Apache Kafka는 ZooKeeper라고 하는 컴포넌트들과 Cluster로 구성되어 있고 Kafka Cluster 내에는 여러개의 Broker들로 구성되어 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NJnGD/btsFDzgSPbV/wEFEoWehpo0wzvlxpCqFw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NJnGD/btsFDzgSPbV/wEFEoWehpo0wzvlxpCqFw0/img.png&quot; data-alt=&quot;참고 : https://velog.io/@hyun6ik/Apache-Kafka-Broker-Zookeeper&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NJnGD/btsFDzgSPbV/wEFEoWehpo0wzvlxpCqFw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNJnGD%2FbtsFDzgSPbV%2FwEFEoWehpo0wzvlxpCqFw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;352&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고 : https://velog.io/@hyun6ik/Apache-Kafka-Broker-Zookeeper&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QDBhZ/btsFDEoWT0D/pxPo5h4LhCr1CX2cFM5RN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QDBhZ/btsFDEoWT0D/pxPo5h4LhCr1CX2cFM5RN1/img.png&quot; data-alt=&quot;참고 : https://www.linkedin.com/pulse/how-deploy-kafka-zookeeper-cluster-linux-based-operating-tiwari/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QDBhZ/btsFDEoWT0D/pxPo5h4LhCr1CX2cFM5RN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQDBhZ%2FbtsFDEoWT0D%2FpxPo5h4LhCr1CX2cFM5RN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;720&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고 : https://www.linkedin.com/pulse/how-deploy-kafka-zookeeper-cluster-linux-based-operating-tiwari/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 zookeeper 를 이용하던것이 초기 broker 관리 모델이고 현재는 자체 관리 모델을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KRaft 프로토콜을 사용하는 Quorum Controller 이 그 자체 관리 모델이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muRDa/btsFAwsmF0G/AqHkKh17bZPeLBjzFc8k00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muRDa/btsFAwsmF0G/AqHkKh17bZPeLBjzFc8k00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muRDa/btsFAwsmF0G/AqHkKh17bZPeLBjzFc8k00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmuRDa%2FbtsFAwsmF0G%2FAqHkKh17bZPeLBjzFc8k00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;569&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709813162067&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;networks:
  kafka_network:

volumes:
  Kafka00:
    driver: local
  Kafka01:
    driver: local
  Kafka02:
    driver: local

services:
  ##Kafka 00
  Kafka00Service:
    image: bitnami/kafka:latest
    restart: unless-stopped
    container_name: Kafka00Container
    ports:
      - '10000:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=0
      - KAFKA_CFG_NODE_ID=0
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@Kafka00Service:9093,1@Kafka01Service:9093,2@Kafka02Service:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://Kafka00Service:9092,EXTERNAL://127.0.0.1:10000
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - &quot;Kafka00:/bitnami/kafka&quot;
  ##Kafka 01
  Kafka01Service:
    image: bitnami/kafka:latest
    restart: unless-stopped
    container_name: Kafka01Container
    ports:
      - '10001:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=1
      - KAFKA_CFG_NODE_ID=1
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@Kafka00Service:9093,1@Kafka01Service:9093,2@Kafka02Service:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://Kafka01Service:9092,EXTERNAL://127.0.0.1:10001
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - &quot;Kafka01:/bitnami/kafka&quot;
  ##Kafka 02
  Kafka02Service:
    image: bitnami/kafka:latest
    restart: unless-stopped
    container_name: Kafka02Container
    ports:
      - '10002:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=2
      - KAFKA_CFG_NODE_ID=2
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@Kafka00Service:9093,1@Kafka01Service:9093,2@Kafka02Service:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://Kafka02Service:9092,EXTERNAL://127.0.0.1:10002
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - &quot;Kafka02:/bitnami/kafka&quot;

  KafkaWebUiService:
    image: provectuslabs/kafka-ui:latest
    restart: unless-stopped
    container_name: KafkaWebUiContainer
    ports:
      - '8080:8080'
    environment:
      - KAFKA_CLUSTERS_0_NAME=Local-Kraft-Cluster
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=Kafka00Service:9092,Kafka01Service:9092,Kafka02Service:9092
      - DYNAMIC_CONFIG_ENABLED=true
      - KAFKA_CLUSTERS_0_AUDIT_TOPICAUDITENABLED=true
      - KAFKA_CLUSTERS_0_AUDIT_CONSOLEAUDITENABLED=true
      #- KAFKA_CLUSTERS_0_METRICS_PORT=9999
    networks:
      - kafka_network&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 아래 명령 실행&lt;/p&gt;
&lt;pre id=&quot;code_1709813491251&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose -f docker-compose.yml up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 localhost:8080 으로 접속하면 다음과 같은 화면이 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boIJ9T/btsFF9QdKZ1/hxg2oCImX2B7pIKtmtVGoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boIJ9T/btsFF9QdKZ1/hxg2oCImX2B7pIKtmtVGoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boIJ9T/btsFF9QdKZ1/hxg2oCImX2B7pIKtmtVGoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboIJ9T%2FbtsFF9QdKZ1%2Fhxg2oCImX2B7pIKtmtVGoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1901&quot; height=&quot;569&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Brokers 를 확인해 보면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPTE3r/btsFFOy4wmH/hYHnOAbsAWKFK77fIB7i11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPTE3r/btsFFOy4wmH/hYHnOAbsAWKFK77fIB7i11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPTE3r/btsFFOy4wmH/hYHnOAbsAWKFK77fIB7i11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPTE3r%2FbtsFFOy4wmH%2FhYHnOAbsAWKFK77fIB7i11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1882&quot; height=&quot;823&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 설치된걸 확인 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 solution 을 하나 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;producer 와 consumer 를 consol app project 로 만들어 추가 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각각에 다음 package 를 설치 하자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710057021844&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package Confluent.Kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 producer 와 consumer 예제이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Producer.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710060284729&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using Confluent.Kafka;

Console.WriteLine(&quot;Producer&quot;);

var config = new ProducerConfig
{
    BootstrapServers = &quot;localhost:10001&quot;, // Kafka broker 주소
    ClientId = &quot;my-producer&quot;
};

using (var producer = new ProducerBuilder&amp;lt;Null, string&amp;gt;(config).Build())
{
    var topic = &quot;my-topic&quot;; // 원하는 토픽 이름
    for (int i = 0; i &amp;lt; 10; i++)
    {
        var message = new Message&amp;lt;Null, string&amp;gt;
        {
            Value = $&quot;Hello, Kafka! Message {Guid.NewGuid().ToString(&quot;N&quot;)}&quot;
        };

        producer.Produce(topic, message, deliveryReport =&amp;gt;
        {
            if (deliveryReport.Error.Code != ErrorCode.NoError)
            {
                Console.WriteLine($&quot;Delivery failed: {deliveryReport.Error.Reason}&quot;);
            }
            else
            {
                Console.WriteLine($&quot;Delivered message to {deliveryReport.TopicPartitionOffset}&quot;);
            }
        });
    }
    producer.Flush();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Consumer&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710060304001&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using Confluent.Kafka;

Console.WriteLine(&quot;Consumer&quot;);
var config = new ConsumerConfig
{
    BootstrapServers = &quot;localhost:10002&quot;, // Kafka broker 주소
    GroupId = &quot;my-consumer-group&quot;, // Consumer Group ID
    AutoOffsetReset = AutoOffsetReset.Earliest // 메시지 읽기 시작 위치 (가장 처음부터)
};

using (var consumer = new ConsumerBuilder&amp;lt;Ignore, string&amp;gt;(config).Build())
{
    var topic = &quot;my-topic&quot;; // 원하는 토픽 이름

    consumer.Subscribe(topic);

    while (true)
    {
        var consumeResult = consumer.Consume(TimeSpan.FromSeconds(1));
        if (consumeResult != null)
        {
            Console.WriteLine($&quot;Received message: {consumeResult.Message.Value}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 BootstraServers 주소를 Producer 는 1001 에 Consumer 는 1002 에 둔것을 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clustering 이 정상동작하는지 확인하기 위해 두 port 를 나누었고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 data 가 전달되는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2287&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnBB0/btsFELJAOpk/edMgyHsR2kXPKgm9IWHfwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnBB0/btsFELJAOpk/edMgyHsR2kXPKgm9IWHfwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnBB0/btsFELJAOpk/edMgyHsR2kXPKgm9IWHfwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnBB0%2FbtsFELJAOpk%2FedMgyHsR2kXPKgm9IWHfwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2287&quot; height=&quot;285&quot; data-origin-width=&quot;2287&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할점 cluster 관련 meta 를 가져오는 과정에서 초기에 10~30 초 가량의 delay 가 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 delay 를 줄일 수 있지만 만약 그렇게 한다면 consumer 의 갯수에 따라 kafka 에 많은 부하를 줄 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/u-1rvwnpUfA&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/u-1rvwnpUfA&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710076955513&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;- YouTube&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://youtu.be/u-1rvwnpUfA&quot; data-og-url=&quot;https://www.youtube.com/watch?v=u-1rvwnpUfA&amp;amp;feature=youtu.be&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://youtu.be/u-1rvwnpUfA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://youtu.be/u-1rvwnpUfA&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;- YouTube&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kafka</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/477</guid>
      <comments>https://yogingang.tistory.com/477#entry477comment</comments>
      <pubDate>Mon, 11 Mar 2024 00:00:30 +0900</pubDate>
    </item>
    <item>
      <title>Dotnet Object Mapper (Automapper vs Mapster)</title>
      <link>https://yogingang.tistory.com/476</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 개발을 하다보면 layer 구분을 하게 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 layer 간에 data 가 공유가 필요하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 데이터를 interface 정의 하기 위해 share layer 등을 두어 처리하게 되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 몇몇 문제가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 layer 에서 user 는 다음과 같이 정의 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708957081509&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class User
{
    public string Name{get;set;}
    public int Age{get;set;}
    public string Email{get;set;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&amp;nbsp; Db 에 저장하기 위해서 orm 에서 persisten model 같은 경우 index 를 위해 다음 같이 정의 할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708957179241&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class User
{
    [Key]
    public Guid Id{get;set;}
    public string Name{get;set;}
    public int Age{get;set;}
    public string Email{get;set;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 위와 같을 경우에 db 쪽 user class 와 domain 쪽 user class 가 다른 문제가 생기게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;물론 이건 이런 상황을 설명하기 위한 예시일 뿐이다.&lt;/b&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 일일이 각 Property 를 matching 해주는게 너무 번거러울 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저런식의 한 두개 class 라면 모르겠으나 layer 간 이동이 잦아지면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점점 저런식의 class 들이 늘어나게 되고 순식간에 수십개가 되기도한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이러한 class 간의 mapping 을 도와주는 library 가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 AutoMapper 라는 것을 많이 썼을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘이 AutoMapper 와 Mapster 라는 두가지 Library 사용법을 알아보겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용에 차이점을 보고 여러분들이 마음에 드는 것을 활용 하시면 되겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AutoMapper&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708960931744&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package AutoMapper&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708960956100&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.AddAutoMapper(Assembly.GetEntryAssembly());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708961002456&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Add
{
    public record Command
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
    }

    public class Response : BaseResponse
    {
        public Guid Id { get; set; }
    }

    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap&amp;lt;Infrastructure.Models.User, Command&amp;gt;()
            .ReverseMap();
        }
    }

    public class CommandHandler(IMapper mapper)
    {
        private readonly IMapper _mapper = mapper;

        public Task&amp;lt;Response&amp;gt; Handle(Command command)
        {
            var user = _mapper.Map&amp;lt;Infrastructure.Models.User&amp;gt;(command);
            Store.Users.Add(user);
            return Task.FromResult(new Response { Result = true, Id=user.Id });
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 보면 생성자에서 mapping 하는 걸 알수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708961028782&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public class MappingProfile : Profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Handler 에서 Object Mapping 을 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708961096999&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var user = _mapper.Map&amp;lt;Infrastructure.Models.User&amp;gt;(command);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Command Type 의 class 가 Models.User&amp;nbsp; class 에 각 property 에 mapping 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mapster&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709032429648&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package Mapster&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709034976008&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetEntryAssembly()!);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709035028920&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AddWithMapster
{
    public record Command
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string EmailAddress { get; set; }
    }

    public class Response : BaseResponse
    {
        public Guid Id { get; set; }
    }

    public class CommandHandler()
    {
        public Task&amp;lt;Response&amp;gt; Handle(Command command)
        {
            var user =command.Adapt&amp;lt;Infrastructure.Models.User&amp;gt;();
            Store.Users.Add(user);
            return Task.FromResult(new Response { Result = true, Id=user.Id });
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자 위에서 보면 command 객체에 extension mehtod 로 Adat 가 있어서 바로 적용하면 user 가 나오게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 mapping 은 같은 이름을 기준으로 가능한 type 들이 mapping 되는 방식인것 같다. (정확하지는 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 위에서는 문제가 하나 있다. EmailAddress 에 대한 처리가 안될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 user 에는 EmailAddress 가 없고 Email 로 정의 되어 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우들이 빈번 하기 때문에 config 이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 추가 하자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709035170735&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MappingRegister : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig&amp;lt;Command, Infrastructure.Models.User&amp;gt;()
              .Map(dest =&amp;gt; dest.Email, src =&amp;gt; src.EmailAddress);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 다시 실행해보면 정상적으로 EmailAddress 도 mapping 되는것을 확인 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoMapper 와 비교해서 편한 부분이 있고 속도가 빠르다고 하는 부분도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 사용할 수 는 있으나 만약 EF Core 와 여러가지로 같이 연계해서 사용하다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoMapper 만큼의 확장성을 갖고 있지는 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분 참고해서 사용 바란다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 취향것!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/RZjf8rwiS0c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/RZjf8rwiS0c&lt;/a&gt;&lt;/p&gt;</description>
      <category>ASPNET</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/476</guid>
      <comments>https://yogingang.tistory.com/476#entry476comment</comments>
      <pubDate>Mon, 4 Mar 2024 00:00:52 +0900</pubDate>
    </item>
    <item>
      <title>MediatR vs Wolverine</title>
      <link>https://yogingang.tistory.com/475</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;dotnet core 에는 여러가지 message library 들이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자체 event 및 delegate 를 통한 처리 방식도 있고 command pattern 을 이용한 구현 방식도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3rd party library 를 사용할 경우 MediatR 을 많이 사용했을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이 MediatR 대신 사용가능한 Wolverine 에 대해 알아보겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 MediatR 로 만들어는 경우는 핵심 코드가 다음과 비슷 할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MediatR 을 사용할 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770603721&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package MediatR&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770709972&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.AddMediatR(cfg =&amp;gt; cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770401878&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; [HttpGet]
 public async Task&amp;lt;IActionResult&amp;gt; Get([FromServices] IMediator mediator)
 {
     var response = await mediator.Send(new Weather.Get.Command());
    
     return Ok(response);
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770437892&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Get
{
    public class Command : IRequest&amp;lt;Response&amp;gt;
    {
    }

    public class Response : WeatherForecast
    {
        public List&amp;lt;WeatherForecast&amp;gt; Forecasts { get; set; }

    }

    public class WeatherForecast
    {
        public DateOnly Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF =&amp;gt; 32 + (int)(TemperatureC / 0.5556);

        public string? Summary { get; set; }
    }

    public class CommandHandler : IRequestHandler&amp;lt;Command, Response&amp;gt;
    {
        private static readonly string[] Summaries = new[]
        {
            &quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;, &quot;Mild&quot;, &quot;Warm&quot;, &quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;, &quot;Scorching&quot;
        };
        public Task&amp;lt;Response&amp;gt; Handle(Command request, CancellationToken cancellationToken)
        {
            var forecasts = Enumerable.Range(1, 5).Select(index =&amp;gt; new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            }).ToList();

            return Task.FromResult(new Response { Forecasts = forecasts });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return type 이 있는 Command 를 선언 하고 (IRequest&amp;lt;Response&amp;gt; 를 Implementation)&lt;/p&gt;
&lt;pre id=&quot;code_1708770470498&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Command : IRequest&amp;lt;Response&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handler 를 선언한 후&amp;nbsp; (IRequestHandler&amp;lt;Command, Response&amp;gt; 를 Implementation)&lt;/p&gt;
&lt;pre id=&quot;code_1708770535193&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CommandHandler : IRequestHandler&amp;lt;Command, Response&amp;gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handle Method 를 Implementation 하자&lt;/p&gt;
&lt;pre id=&quot;code_1708770645662&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Task&amp;lt;Response&amp;gt; Handle(Command request, CancellationToken cancellationToken)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 하면 MediatR 을 이용하여 Controller 에서 Handler 쪽으로 Message 가 전달 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Wolverine 을 사용 할 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770797921&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package WolverineFx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708770833630&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Host.UseWolverine()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자이제 여기서 차이점이 있는데 wolverine 같은 경우는 command 를 특정한 interface 를 이용하여 구현하지 않아도 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708770915804&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public class Command {} // 이게 다다.. 빈 class 하나 선언하면 된다. 
 public record Command {} // record 도 가능하다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return type 도 필요 없고 IRequest 같은 interface 도 필요 없다. (의존성 없음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handler 도 마찬가지다 아래를 확인 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Handler.cs&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708771146994&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public static Task&amp;lt;Response&amp;gt; Handle(Command request)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 주의 할것은 있다. Handle 의 이름을 다른 형태로 변경하면 오류가 날 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 source generator 같은 것으로 동적으로 처리 코드를 compile 시점에 생성하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래와 같은 코드가 generated 된 code 에 존재하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708771221742&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// The actual message execution
var outgoing1 = await Wolverine.Features.Weather.GetWithWolverine.CommandHandler.Handle(command).ConfigureAwait(false);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 사용하면 wolverine 을 통해 Message 가 전달될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이제 문제가 하나 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wolverine 으로 실행한 프로젝트인 경우 처음 실행 시에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2~3 초의 message (cold stat) 전달 시간이 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는&amp;nbsp; 위에 handler 들을 찾아서 source 를 동적으로 생성하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 첫 실행 이후는 아주 빠르게 동작하지만 .. 우리가 원하는 동작은 아니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우를 예방하기 위에 다음과 같은 option 을 wolverine 에 주자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708771515735&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Host.UseWolverine(opts=&amp;gt;
{
    opts.ApplicationAssembly = Assembly.GetExecutingAssembly();
    opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 실행해 보자 . 그런데 실행해 보면 처음 실행이 느린게 똑같은걸 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화내지 말고 침작 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;solution 을 확인해보자 다음과 같은 folder 와 code 들이 추가되어 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8D8QE/btsFg16lhXl/oNjwmq1jgAfbUSz9DEJHb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8D8QE/btsFg16lhXl/oNjwmq1jgAfbUSz9DEJHb0/img.png&quot; data-alt=&quot;Auto generated code&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8D8QE/btsFg16lhXl/oNjwmq1jgAfbUSz9DEJHb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8D8QE%2FbtsFg16lhXl%2FoNjwmq1jgAfbUSz9DEJHb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;129&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Auto generated code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 project 를 다운 시켰다가 다시 실행해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행에도 무척 빠르게 실행되는것을 확인 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상태에서 TypeLoadMode.Static 으로 변경해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 처음 실행에도 아주 빠르게 실행되는 것을 확인 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그런데 이런경우 문제가 또 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cold start 를 방지하기 위해 모든 handler 들을 다 실행해 봐야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 수정이라도 하게 되면 다시 그 과정을 거쳐댜 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 불편하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사실 wolverine 은 미리 코드를 생성하도록 지원하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 다음과 같이 수정하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Program.cs&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709119790534&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//app.Run();
await app.RunOaktonCommands(args);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 project folder 로 이동해서 command line 을 이용해서 다음을 입력하자&lt;/p&gt;
&lt;pre id=&quot;code_1709119829647&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet run -- codegen write&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하게 되면 모든 handler 가 자동으로 생성되게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eekXnq/btsFmhaluit/AokBHtQBy0RQx1TVUJ0yVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eekXnq/btsFmhaluit/AokBHtQBy0RQx1TVUJ0yVK/img.png&quot; data-alt=&quot;Handler 가 자동으로 생성되는 것을 알수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eekXnq/btsFmhaluit/AokBHtQBy0RQx1TVUJ0yVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeekXnq%2FbtsFmhaluit%2FAokBHtQBy0RQx1TVUJ0yVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;252&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Handler 가 자동으로 생성되는 것을 알수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 FluenentValidtion 이라는 validation check library 와도 연계된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709120160306&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package WolverineFx.FluentValidation&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 설정을 추가 하자&lt;/p&gt;
&lt;pre id=&quot;code_1709120205577&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Host.UseWolverine(opts =&amp;gt;
{
	...
    opts.UseFluentValidation();
    opts.Services.AddSingleton(typeof(IFailureAction&amp;lt;&amp;gt;), typeof(CustomFailureAction&amp;lt;&amp;gt;));
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709120214742&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CustomFailureAction&amp;lt;T&amp;gt; : IFailureAction&amp;lt;T&amp;gt;
{
    public void Throw(T message, IReadOnlyList&amp;lt;ValidationFailure&amp;gt; failures)
    {
        throw new Exception(&quot;Validation failed!: &quot; + failures.Select(x =&amp;gt; x.ErrorMessage).Join(&quot;, &quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handler 에 다음 코드도 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709120304732&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Validator : AbstractValidator&amp;lt;Command&amp;gt;
{
    public Validator()
    {
        RuleFor(c =&amp;gt; c.EmailAddress).NotNull().NotEmpty();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행시 EmailAddress 가 null 이거나 empty 이면 error 가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MediatR 을 오랫동안 사용해본 입장에서 조금더 편리해진 library 라는 생각이 든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 오래전에 만들어진 library 이기도 하여 MediatR 을 변경해봐야 하는 시점이 된것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 정말 편리하게 사용했던 library 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 고마웠다. ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;fare well MediatR&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/B0bTMmbRFAQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/B0bTMmbRFAQ&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ASPNET</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/475</guid>
      <comments>https://yogingang.tistory.com/475#entry475comment</comments>
      <pubDate>Mon, 26 Feb 2024 00:00:55 +0900</pubDate>
    </item>
    <item>
      <title>Blazor web app  렌더링 간 data 공유</title>
      <link>https://yogingang.tistory.com/474</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Blazor web app 의 server 와 client 간의 rendering 을 함께 사용하다 보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;component 와 data 공유를 하는 방법이 필요 하다. (state management 라고도한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blazor 가 기존의 Server only 또는 Client only (WebAssembly)&amp;nbsp; 같은 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state management 가 특별히 어려운 건 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 memory 상에서 처리 하면 되는 문제라서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우는 그냥 기존대로 사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 Blazor 이 united 라는 새로운 template 를 만들면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;server 와 client 가 통합되어 사용되면 문제가 생기게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 현재 server 에서 client 로 data 를 전달하는 standard 한 방법이 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS 에서도 이야기 하지 않고 그들이 이야기 한데로 적용해도 동작하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에 Prerender (SSR) 가 활성화 되어 있는 경우 그에 의한 data 두번 호출에 문제도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 이러 한 문제점들을 확인 하고 Blazor Web App 에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rendering mode 간에 data share 를 하는 방법을 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 project 생성시 만들어지는 Counter 앱을 기준으로 설명 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. prerender 시 data 처리 (중복 실행되지 않게 하기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행시 SSR 모드로 실행되면 (Prerender) 이때 api 를 호출 하거나 하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 실행시 (interactive mode) api 를 다시 호출하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해서 PersitenComponent 를 활용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708155584805&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@page &quot;/counter/prerendered&quot;
@implements IDisposable
@inject ILogger&amp;lt;PrerenderedCounter&amp;gt; Logger
@inject PersistentComponentState ApplicationState
@rendermode InteractiveAuto

&amp;lt;PageTitle&amp;gt;Prerendered Counter&amp;lt;/PageTitle&amp;gt;

&amp;lt;h1&amp;gt;Prerendered Counter, @renderMode&amp;lt;/h1&amp;gt;

&amp;lt;p role=&quot;status&quot;&amp;gt;@message  @currentCount&amp;lt;/p&amp;gt;

&amp;lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;

@code {
    private int currentCount;
    private string? message;
    private Random r = new Random();
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        persistingSubscription =ApplicationState.RegisterOnPersisting(PersistCount);

        if (!ApplicationState.TryTakeFromJson&amp;lt;int&amp;gt;(
            &quot;count&quot;, out var restoredCount))
        {
            currentCount = r.Next(100);
            message = &quot;currentCount set to &quot;;
            Logger.LogInformation(message + currentCount ); 
        }
        else
        {
            currentCount = restoredCount!;
            message = &quot;currentCount restored to &quot;;
            Logger.LogInformation(message + currentCount);
        }
    }

    private string renderMode = &quot;SSR (not interactive)&quot;;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            if (OperatingSystem.IsBrowser())
            {
                renderMode = &quot;WebAssembly interactive&quot;;
            }
            else
            {
                renderMode = &quot;Server interactive&quot;;
            }
            await base.OnAfterRenderAsync(firstRender);
            StateHasChanged();
        }
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(&quot;count&quot;, currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 저장하고&lt;/p&gt;
&lt;pre id=&quot;code_1708155626043&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ApplicationState.PersistAsJson(&quot;count&quot;, currentCount);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 저장된 값을 가져온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708155666300&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ApplicationState.TryTakeFromJson&amp;lt;int&amp;gt;(&quot;count&quot;, out var restoredCount)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. in memory 상에 data 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 위와 같이 prerender 처리를 해도 counter 를 increment 한 후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;home 으로 이동했다가 다시 돌아오면 이전 값이 남아 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결해 보자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708155766967&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@page &quot;/counter/inmemory&quot;
@rendermode InteractiveAuto
@inject IStateContainer&amp;lt;int&amp;gt; StateContainer
@implements IDisposable

&amp;lt;PageTitle&amp;gt;Inmemory Counter&amp;lt;/PageTitle&amp;gt;

&amp;lt;h1&amp;gt;Inmemory Counter, @renderMode!&amp;lt;/h1&amp;gt;

&amp;lt;p role=&quot;status&quot;&amp;gt;Current count: @currentCount &amp;lt;/p&amp;gt;

&amp;lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;

@code {
    private int currentCount = 0;
    private string renderMode = &quot;SSR (not interactive)&quot;;

    private void  IncrementCount()
    {
        StateContainer.Property =  ++currentCount;
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            if (OperatingSystem.IsBrowser())
            {
                renderMode = &quot;WebAssembly interactive&quot;;
            }
            else
            {
                renderMode = &quot;Server interactive&quot;;
            }
            base.OnAfterRender(firstRender);
            StateHasChanged();
        }
    }

    protected override void OnInitialized()
    {
        StateContainer.OnChange -= StateHasChanged;
        StateContainer.OnChange += StateHasChanged;
        currentCount = StateContainer.Property;
        base.OnInitialized();
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 저장을 위한 StateContainer 는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708155805086&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IStateContainer&amp;lt;T&amp;gt;
{
    T? Property { get; set; }
    event Action? OnChange;
}
public class StateContainer&amp;lt;T&amp;gt;:IStateContainer&amp;lt;T&amp;gt;
{
    private T? _value;

    public T? Property
    {
        get =&amp;gt; _value;
        set
        {
            _value = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() =&amp;gt; OnChange?.Invoke();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Server 와 client 에 각각 service 를 등록 하자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708155888760&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//server 쪽 program.cs
builder.Services.AddScoped(typeof(IStateContainer&amp;lt;&amp;gt;), typeof(StateContainer&amp;lt;&amp;gt;));


//client 쪽 program.cs (webassembly 는 scope == singleton 과 같다.)
builder.Services.AddSingleton(typeof(IStateContainer&amp;lt;&amp;gt;), typeof(StateContainer&amp;lt;&amp;gt;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Render Mode 간에 Data 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 해도 의문이 든다. Server Interactive 한 상태에서 increment 한 data 는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client interactive 한 mode 로 전달이 되지 않는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 두 app (Server, WebAssembly) 은 memory 상에 data 를 공유하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할수 있는 방법이 사실 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이부분에서 ms 는 뭔가 방법을 알려줘야 할 것 같은데 특별이 방법은 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들이 만든 예제에서 된다고 하는 내용을 보면 실제 실행시 적용되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 이 부분을 염두하지 못한건지? 아니면 내가 찾지 못한 것 일수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이걸 처리 위해 brower 에 sessionstorage 를 이용할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blazor server 에서는 protected 된 storage 가 있지만 webassembly 는 사용 불가능 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 3rd party nuget package 를 이용할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client project 로 이동해서 다음 명령을 실행하자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708156233247&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dotnet add package Blazored.SessionStorage&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Program.cs (client) 로 이동해서 다음을 추가하자&lt;/p&gt;
&lt;pre id=&quot;code_1708156259221&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.AddBlazoredSessionStorage();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shared 처리하는 counter 는 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708156310861&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@page &quot;/counter/shared&quot;
@* @rendermode @(new InteractiveAutoRenderMode(prerender: false)) *@
@rendermode InteractiveAuto
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage

&amp;lt;PageTitle&amp;gt;Shared Counter&amp;lt;/PageTitle&amp;gt;

&amp;lt;h1&amp;gt;Counter, @renderMode!&amp;lt;/h1&amp;gt;

&amp;lt;p role=&quot;status&quot;&amp;gt;Current count: @currentCount &amp;lt;/p&amp;gt;

&amp;lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;

@code {
    private int currentCount = 0;
    private string renderMode = &quot;SSR (not interactive)&quot;;

    private async Task IncrementCount()
    {
        currentCount = await sessionStorage.GetItemAsync&amp;lt;int&amp;gt;(&quot;count&quot;); 
        await sessionStorage.SetItemAsync(&quot;count&quot;, ++currentCount);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            if (OperatingSystem.IsBrowser())
            {
                renderMode = &quot;WebAssembly interactive&quot;;
            }
            else
            {
                renderMode = &quot;Server interactive&quot;;
            }
            currentCount = await sessionStorage.GetItemAsync&amp;lt;int&amp;gt;(&quot;count&quot;) == 0 ? currentCount : await sessionStorage.GetItemAsync&amp;lt;int&amp;gt;(&quot;count&quot;);
            await base.OnAfterRenderAsync(firstRender);
            StateHasChanged();
        }
    }

    // protected override async Task OnInitializedAsync()
    // {
    //     // currentCount = await sessionStorage.GetItemAsync&amp;lt;int&amp;gt;(&quot;count&quot;) == 0 ? currentCount : await sessionStorage.GetItemAsync&amp;lt;int&amp;gt;(&quot;count&quot;);
    //     // StateHasChanged();
    //     // await base.OnInitializedAsync();
    // }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석 처리되어 있는 부분은 만약 prerender를 false 로 설정 하였을때 OnInitializeAsync 를 이용해서 처리 가능하다는 것을 보여준 것이다. 이제 data 가 공유가 가능해 졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS 에서 야심차게 준비한 Blazor Web App 이라는 통합(??) web frameworks 는 아직 많이 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들이 어디까지 생각하고 이것을 개발 한 것인지 모르겠으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자도 그렇고 개발사도 그렇고 좀더 시간이 필요할 걸로 보인다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들은 각 mode 들이 어떻게 서로 상호작용하고 동작하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 파고들지 않는다면 많은 문제에 직면할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그러한 수고를 할 가치가 있는 web frameworks 임에는 분명하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS 가 조금더 빠르게 대응 하여 각각에 대한 reference 를 준비해야 할 거라고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 하지 않는다면 이미 사용하고 있는 frontend frameworks 에서 굳이 이동하려는 사람들은 없을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/cmFdyeRAja8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/cmFdyeRAja8&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ASPNET/Blazor</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/474</guid>
      <comments>https://yogingang.tistory.com/474#entry474comment</comments>
      <pubDate>Mon, 19 Feb 2024 00:00:52 +0900</pubDate>
    </item>
    <item>
      <title>Dotnet 8 - native aot</title>
      <link>https://yogingang.tistory.com/472</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Native AOT 란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 컴파일 시점에서 AOT 컴파일 11을 수행해서 바이트 코드(IL)가 아닌 네이티브 코드로 컴파일 하는 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시작&amp;nbsp;실행&amp;nbsp;시&amp;nbsp;JIT(Just-In-Time)&amp;nbsp;컴파일&amp;nbsp;과정이&amp;nbsp;없으므로&amp;nbsp;빠르게&amp;nbsp;프로그램&amp;nbsp;시작&lt;/li&gt;
&lt;li&gt;JIT 과정이 없으므로 메모리 사용량이 보다 낮음&lt;/li&gt;
&lt;li&gt;런타임 라이브러리 의존성이 없는 실행 파일 생성&lt;/li&gt;
&lt;li&gt;타 프로그래밍 언어에 C 스타일로 라이브러리 제공 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능비교&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buImKN/btsCN0izEq0/9skQN3sCutKqyEKD0v98y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buImKN/btsCN0izEq0/9skQN3sCutKqyEKD0v98y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buImKN/btsCN0izEq0/9skQN3sCutKqyEKD0v98y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuImKN%2FbtsCN0izEq0%2F9skQN3sCutKqyEKD0v98y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;743&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 기본적으로 위와 같이 빠른 성능을 내는 것으로 나오지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 실행시 조금 다를 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Native aot 는 초기실행에 성능 이점이 있으나 stress test 등에서는 runtime 보다 느린 경우가 많다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 dynamic PGO 같은 jit 관련 runtime 최적화를 수행하기 힘들기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 앞으로 좀더 performance 관련하여 성능향상이 있을것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#tiering-and-dynamic-pgo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#tiering-and-dynamic-pgo&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704026548146&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Performance Improvements in .NET 8 - .NET Blog&quot; data-og-description=&quot;.NET 7 was super fast, .NET 8 is faster. Take an in-depth tour through over 500 pull requests that make that a reality.&quot; data-og-host=&quot;devblogs.microsoft.com&quot; data-og-source-url=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#tiering-and-dynamic-pgo&quot; data-og-url=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VDdUa/hyUTJCUr9i/wg5Q1qK146ftxGPCl2kUjK/img.png?width=1207&amp;amp;height=703&amp;amp;face=0_0_1207_703,https://scrap.kakaocdn.net/dn/cYwVpD/hyUXO3CYAV/Gi86FiEnRmOrlr8rZhmEm0/img.png?width=853&amp;amp;height=912&amp;amp;face=0_0_853_912,https://scrap.kakaocdn.net/dn/crtTKZ/hyUTLU1Aar/PgIaeJ9vNl04W4UciGVldk/img.png?width=1225&amp;amp;height=493&amp;amp;face=0_0_1225_493&quot;&gt;&lt;a href=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#tiering-and-dynamic-pgo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#tiering-and-dynamic-pgo&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VDdUa/hyUTJCUr9i/wg5Q1qK146ftxGPCl2kUjK/img.png?width=1207&amp;amp;height=703&amp;amp;face=0_0_1207_703,https://scrap.kakaocdn.net/dn/cYwVpD/hyUXO3CYAV/Gi86FiEnRmOrlr8rZhmEm0/img.png?width=853&amp;amp;height=912&amp;amp;face=0_0_853_912,https://scrap.kakaocdn.net/dn/crtTKZ/hyUTLU1Aar/PgIaeJ9vNl04W4UciGVldk/img.png?width=1225&amp;amp;height=493&amp;amp;face=0_0_1225_493');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Performance Improvements in .NET 8 - .NET Blog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;.NET 7 was super fast, .NET 8 is faster. Take an in-depth tour through over 500 pull requests that make that a reality.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devblogs.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visual studio 2022 에서 project template 에서 아래 그림에 있는 template 을 선택하자&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT4JzI/btsCTCnbebM/umfwFhTDsCGlbF2blZnFwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT4JzI/btsCTCnbebM/umfwFhTDsCGlbF2blZnFwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT4JzI/btsCTCnbebM/umfwFhTDsCGlbF2blZnFwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT4JzI%2FbtsCTCnbebM%2FumfwFhTDsCGlbF2blZnFwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;147&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 간단한 web api project 가 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 project 와 몇가지 다른 점이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 CreateBuilder 가 CreateSlimBuilder 로 변경 되었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704026805551&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var builder = WebApplication.CreateSlimBuilder(args);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래와 같이 serializer option 이 추가 되었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704026830456&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;builder.Services.ConfigureHttpJsonOptions(options =&amp;gt;
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Native AOT 가 Reflection 을 처리 하지 못하기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonSerializer 에 대한 SourceGenerator level 에서의 구현이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704026965695&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launchSettings.json 변경사항&lt;/p&gt;
&lt;pre id=&quot;code_1704027049105&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;$schema&quot;: &quot;http://json.schemastore.org/launchsettings.json&quot;,
-  &quot;iisSettings&quot;: {
-     &quot;windowsAuthentication&quot;: false,
-     &quot;anonymousAuthentication&quot;: true,
-     &quot;iisExpress&quot;: {
-       &quot;applicationUrl&quot;: &quot;http://localhost:11152&quot;,
-       &quot;sslPort&quot;: 0
-     }
-   },
  &quot;profiles&quot;: {
    &quot;http&quot;: {
      &quot;commandName&quot;: &quot;Project&quot;,
      &quot;dotnetRunMessages&quot;: true,
      &quot;launchBrowser&quot;: true,
      &quot;launchUrl&quot;: &quot;todos&quot;,
      &quot;applicationUrl&quot;: &quot;http://localhost:5102&quot;,
        &quot;environmentVariables&quot;: {
          &quot;ASPNETCORE_ENVIRONMENT&quot;: &quot;Development&quot;
        }
      },
-     &quot;IIS Express&quot;: {
-       &quot;commandName&quot;: &quot;IISExpress&quot;,
-       &quot;launchBrowser&quot;: true,
-       &quot;launchUrl&quot;: &quot;todos&quot;,
-      &quot;environmentVariables&quot;: {
-       &quot;ASPNETCORE_ENVIRONMENT&quot;: &quot;Development&quot;
-      }
-    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIS 관련 setting 이 전부 삭제되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행해 보면 정상적으로 실행 될것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;csproj 파일을 열고 다음 속성을 추가 해보자&lt;/p&gt;
&lt;pre id=&quot;code_1704027277261&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&amp;gt;

  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;!-- Other properties omitted for brevity --&amp;gt;
    &amp;lt;EmitCompilerGeneratedFiles&amp;gt;true&amp;lt;/EmitCompilerGeneratedFiles&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;

&amp;lt;/Project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 속성을 추가하고 true 를 설정하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obj/Debug/net8.0/generated/&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; 밑에 sourcegenerator 에 의해 생성된 code 들을 확인 할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련영상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/EjjY_Nf7y3U&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/EjjY_Nf7y3U&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ASPNET</category>
      <author>yogingang</author>
      <guid isPermaLink="true">https://yogingang.tistory.com/472</guid>
      <comments>https://yogingang.tistory.com/472#entry472comment</comments>
      <pubDate>Mon, 1 Jan 2024 00:00:29 +0900</pubDate>
    </item>
  </channel>
</rss>