๐Ÿข ๊ธฐ์—…์šฉ C# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ์ „๋žต ๐Ÿš€

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿข ๊ธฐ์—…์šฉ C# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ์ „๋žต ๐Ÿš€

 

 

์•ˆ๋…•ํ•˜์„ธ์š”, ์—ฌ๋Ÿฌ๋ถ„! ์˜ค๋Š˜์€ ์ข€ ํŠน๋ณ„ํ•œ ์ฃผ์ œ๋กœ ์ฐพ์•„์™”์–ด์š”. ๋ฐ”๋กœ "๊ธฐ์—…์šฉ C# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ์ „๋žต"์— ๋Œ€ํ•ด ์–˜๊ธฐํ•ด๋ณผ ๊ฑฐ์˜ˆ์š”. ์–ด๋งˆ์–ด๋งˆํ•˜๊ฒŒ ๋ณต์žกํ•ด ๋ณด์ด๋Š” ์ด ์ฃผ์ œ, ๊ณผ์—ฐ ์šฐ๋ฆฌ๊ฐ€ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ๋‹น์—ฐํ•˜์ฃ ! ์ œ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ๋“ ๋“ ํ•œ ๊ฐ€์ด๋“œ๊ฐ€ ๋˜์–ด ๋“œ๋ฆด๊ฒŒ์š”. ๐Ÿ˜Ž

์šฐ์„ , ์ด ์ฃผ์ œ๊ฐ€ ์™œ ์ค‘์š”ํ•œ์ง€ ์•„์‹œ๋‚˜์š”? ์š”์ฆ˜ IT ์—…๊ณ„์—์„œ๋Š” "๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ"๋ผ๋Š” ๊ฐœ๋…์ด ์ •๋ง ํ•ซํ•ด์š”! ๋งˆ์น˜ ์žฌ๋Šฅ๋„ท์—์„œ ๋‹ค์–‘ํ•œ ์žฌ๋Šฅ์„ ํ•œ ๊ณณ์—์„œ ๊ฑฐ๋ž˜ํ•˜๋“ฏ์ด, ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์—ฌ๋Ÿฌ ๊ณ ๊ฐ(ํ…Œ๋„ŒํŠธ)๋ฅผ ์„œ๋น„์Šคํ•˜๋Š” ๊ฑฐ์ฃ . ์ด๊ฒŒ ๋ฐ”๋กœ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ์˜ ํ•ต์‹ฌ์ด์—์š”!

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ๊ณ ๊ณ ์”ฝ~! ๐Ÿš€

1. ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ž€ ๋ญ์•ผ? ๐Ÿค”

๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ... ๋ญ”๊ฐ€ ์–ด๋ ค์›Œ ๋ณด์ด๋Š” ์ด ๋‹จ์–ด, ์‚ฌ์‹ค ๊ทธ๋ ‡๊ฒŒ ๋ณต์žกํ•œ ๊ฐœ๋…์ด ์•„๋‹ˆ์—์š”! ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”.

๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ(Multi-tenancy)๋ž€? ํ•˜๋‚˜์˜ ์†Œํ”„ํŠธ์›จ์–ด ์ธ์Šคํ„ด์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ณ ๊ฐ(ํ…Œ๋„ŒํŠธ)์„ ๋™์‹œ์— ์„œ๋น„์Šคํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋งํ•ด์š”.

์Œ... ์•„์ง๋„ ์ข€ ์–ด๋ ต๋‚˜์š”? ๊ทธ๋Ÿผ ๋” ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด๋ณผ๊ฒŒ์š”! ๐Ÿ 

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

๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋„ ์ด์™€ ๋น„์Šทํ•ด์š”! ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์•„ํŒŒํŠธ ๊ฑด๋ฌผ)์ด ์—ฌ๋Ÿฌ ๊ณ ๊ฐ(์ž…์ฃผ ๊ฐ€๊ตฌ)์„ ๋™์‹œ์— ์„œ๋น„์Šคํ•˜๋Š” ๊ฑฐ์˜ˆ์š”. ๊ฐ ๊ณ ๊ฐ์€ ์ž์‹ ๋งŒ์˜ ๋…๋ฆฝ๋œ ๊ณต๊ฐ„(๋ฐ์ดํ„ฐ, ์„ค์ • ๋“ฑ)์„ ๊ฐ€์ง€์ง€๋งŒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฝ”๋“œ๋‚˜ ์„œ๋ฒ„ ์ž์›์€ ๊ณต์œ ํ•˜๋Š” ๊ฑฐ์ฃ .

๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์•„ํŒŒํŠธ ๋น„์œ  ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์•„ํŒŒํŠธ ํ…Œ๋„ŒํŠธ A ํ…Œ๋„ŒํŠธ B ํ…Œ๋„ŒํŠธ C ํ…Œ๋„ŒํŠธ D ํ…Œ๋„ŒํŠธ E ํ…Œ๋„ŒํŠธ F

์ดํ•ด๊ฐ€ ์ข€ ๋˜์…จ๋‚˜์š”? ๐Ÿ‘€ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ์˜ ์žฅ์ ์€ ๋ญ˜๊นŒ์š”?

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

ํ•˜์ง€๋งŒ ์žฅ์ ๋งŒ ์žˆ๋Š” ๊ฑด ์•„๋‹ˆ์—์š”. ๋‹จ์ ๋„ ์žˆ์ฃ !

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

์ž, ์ด์ œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๊ฐ€ ๋ญ”์ง€ ๋Œ€์ถฉ ๊ฐ์ด ์˜ค์‹œ์ฃ ? ๐Ÿ‘ ๊ทธ๋Ÿผ ์ด์ œ C#์—์„œ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ์š”? ํฅ๋ฏธ์ง„์ง„ํ•œ ์—ฌ์ •์ด ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์–ด์š”! ๋‹ค์Œ ์„น์…˜์œผ๋กœ ๊ณ ๊ณ ์”ฝ~! ๐Ÿš€

2. C#์—์„œ์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ๊ธฐ๋ณธ ์ „๋žต ๐Ÿ› ๏ธ

์ž, ์ด์ œ C#์—์„œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ ์ฐจ๋ก€์˜ˆ์š”. ์—ฌ๋Ÿฌ๋ถ„, ์ค€๋น„๋˜์…จ๋‚˜์š”? ๐Ÿ˜Ž

C#์—์„œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์–ด์š”.

C#์—์„œ์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ
  2. ์Šคํ‚ค๋งˆ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ
  3. ํ–‰ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ

๊ฐ๊ฐ์˜ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊นŒ์š”? ๊ณ ๊ณ ์”ฝ~! ๐Ÿš€

2.1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ

์ด ๋ฐฉ๋ฒ•์€ ๊ฐ ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์˜ˆ์š”. ๋งˆ์น˜ ๊ฐ ๊ฐ€๊ตฌ๋งˆ๋‹ค ๋ณ„๋„์˜ ์ฐฝ๊ณ ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ์š”!

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„ ํ…Œ๋„ŒํŠธ A DB ํ…Œ๋„ŒํŠธ B DB ํ…Œ๋„ŒํŠธ C DB

์ด ๋ฐฉ๋ฒ•์˜ ์žฅ๋‹จ์ ์„ ์‚ดํŽด๋ณผ๊นŒ์š”?

  • ์žฅ์ :
    • ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ๊ฐ€ ์™„๋ฒฝํ•ด์„œ ๋ณด์•ˆ์„ฑ์ด ๋†’์•„์š”.
    • ๊ฐ ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๋งž์ถค ์„ค์ •์ด ์‰ฌ์›Œ์š”.
    • ํ•œ ํ…Œ๋„ŒํŠธ์˜ ๋ฌธ์ œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ์—๊ฒŒ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•„์š”.
  • ๋‹จ์ :
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์–ด์š”.
    • ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ์ด ๋น„ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์–ด์š”.
    • ํ…Œ๋„ŒํŠธ ์ˆ˜๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์š”.

C#์—์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ์š”?


public class DatabaseConnectionFactory
{
    public IDbConnection CreateConnection(string tenantId)
    {
        string connectionString = GetConnectionStringForTenant(tenantId);
        return new SqlConnection(connectionString);
    }

    private string GetConnectionStringForTenant(string tenantId)
    {
        // ํ…Œ๋„ŒํŠธ ID์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜
        // ์‹ค์ œ๋กœ๋Š” ์„ค์ • ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        return $"Server=myServerAddress;Database={tenantId}Db;User Id=myUsername;Password=myPassword;";
    }
}

์ด ์ฝ”๋“œ๋Š” ํ…Œ๋„ŒํŠธ ID์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํด๋ž˜์Šค์˜ˆ์š”. ๊ฐ ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์ฃ !

2.2 ์Šคํ‚ค๋งˆ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ

์ด ๋ฐฉ๋ฒ•์€ ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์•ˆ์—์„œ ๊ฐ ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๋ณ„๋„์˜ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•ด์š”. ์•„ํŒŒํŠธ๋กœ ์น˜๋ฉด ๊ฐ ๊ฐ€๊ตฌ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ธต์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜์ฃ !

์Šคํ‚ค๋งˆ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‹จ์ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ๋„ŒํŠธ A ์Šคํ‚ค๋งˆ ํ…Œ๋„ŒํŠธ B ์Šคํ‚ค๋งˆ ํ…Œ๋„ŒํŠธ C ์Šคํ‚ค๋งˆ

์ด ๋ฐฉ๋ฒ•์˜ ์žฅ๋‹จ์ ์€ ๋ญ˜๊นŒ์š”?

  • ์žฅ์ :
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ๋ณด๋‹ค ๋ฆฌ์†Œ์Šค๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.
    • ํ…Œ๋„ŒํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ๊ฐ€ ๋ช…ํ™•ํ•ด์š”.
    • ๋ฐฑ์—…๊ณผ ๋ณต์›์ด ๋น„๊ต์  ์‰ฌ์›Œ์š”.
  • ๋‹จ์ :
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ๋งŒํผ ์™„๋ฒฝํ•œ ๊ฒฉ๋ฆฌ๋Š” ์•„๋‹ˆ์—์š”.
    • ์Šคํ‚ค๋งˆ ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์–ด์š”.
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ „์ฒด์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ์ž‘์—… ์‹œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด์š”.

C#์—์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ์š”?


public class SchemaConnectionFactory
{
    public IDbConnection CreateConnection(string tenantId)
    {
        string connectionString = "Server=myServerAddress;Database=myDatabase;User Id=myUsername;Password=myPassword;";
        var connection = new SqlConnection(connectionString);
        connection.Open();
        
        // ํ…Œ๋„ŒํŠธ์— ํ•ด๋‹นํ•˜๋Š” ์Šคํ‚ค๋งˆ๋กœ ์ „ํ™˜
        using (var command = connection.CreateCommand())
        {
            command.CommandText = $"SET SCHEMA '{tenantId}'";
            command.ExecuteNonQuery();
        }
        
        return connection;
    }
}

์ด ์ฝ”๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•œ ํ›„, ํ…Œ๋„ŒํŠธ ID์— ํ•ด๋‹นํ•˜๋Š” ์Šคํ‚ค๋งˆ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฑฐ์˜ˆ์š”. ๊ฐ ํ…Œ๋„ŒํŠธ๋Š” ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์ฃ !

2.3 ํ–‰ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ

์ด ๋ฐฉ๋ฒ•์€ ๋ชจ๋“  ํ…Œ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๊ณ , ํ…Œ๋„ŒํŠธ ID ์ปฌ๋Ÿผ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด์š”. ์•„ํŒŒํŠธ๋กœ ์น˜๋ฉด ๋ชจ๋“  ๊ฐ€๊ตฌ๊ฐ€ ๊ฐ™์€ ์ฐฝ๊ณ ๋ฅผ ์‚ฌ์šฉํ•˜๋˜, ๊ฐ์ž์˜ ๋ฌผ๊ฑด์— ์ด๋ฆ„ํ‘œ๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•ด์š”!

ํ–‰ ์ˆ˜์ค€์˜ ๋ถ„๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ํ…Œ๋„ŒํŠธ ID ๋ฐ์ดํ„ฐ A A์˜ ๋ฐ์ดํ„ฐ B B์˜ ๋ฐ์ดํ„ฐ C C์˜ ๋ฐ์ดํ„ฐ

์ด ๋ฐฉ๋ฒ•์˜ ์žฅ๋‹จ์ ์€ ๋ญ˜๊นŒ์š”?

  • ์žฅ์ :
    • ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ์ด ๊ฐ€์žฅ ํšจ์œจ์ ์ด์—์š”.
    • ๊ตฌํ˜„์ด ๋น„๊ต์  ๊ฐ„๋‹จํ•ด์š”.
    • ํ…Œ๋„ŒํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ ๊ฐ€ ์‰ฌ์›Œ์š”.
  • ๋‹จ์ :
    • ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ๊ฐ€ ๋…ผ๋ฆฌ์  ์ˆ˜์ค€์—์„œ๋งŒ ์ด๋ฃจ์–ด์ ธ์„œ ๋ณด์•ˆ ๋ฆฌ์Šคํฌ๊ฐ€ ์žˆ์–ด์š”.
    • ์ฟผ๋ฆฌ๋งˆ๋‹ค ํ…Œ๋„ŒํŠธ ID ํ•„ํ„ฐ๋ง์ด ํ•„์š”ํ•ด์„œ ์‹ค์ˆ˜ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์–ด์š”.
    • ํ…Œ๋„ŒํŠธ๋ณ„ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์ด ์–ด๋ ค์›Œ์š”.

C#์—์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ์š”?


public class RowLevelTenantRepository
{
    private readonly string _connectionString;
    private readonly string _tenantId;

    public RowLevelTenantRepository(string connectionString, string tenantId)
    {
        _connectionString = connectionString;
        _tenantId = tenantId;
    }

    public IEnumerable<user> GetUsers()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            using (var command = connection.CreateCommand())
            {
                command.CommandText = "SELECT * FROM Users WHERE TenantId = @TenantId";
                command.Parameters.AddWithValue("@TenantId", _tenantId);
                
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        yield return new User
                        {
                            Id = reader.GetInt32(reader.GetOrdinal("Id")),
                            Name = reader.GetString(reader.GetOrdinal("Name"))
                        };
                    }
                }
            }
        }
    }
}
</user>

์ด ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ํ•ญ์ƒ ํ…Œ๋„ŒํŠธ ID๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฑฐ์˜ˆ์š”. ๋ชจ๋“  ํ…Œ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ์žˆ์ง€๋งŒ, ๊ฐ ํ…Œ๋„ŒํŠธ๋Š” ์ž์‹ ์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์ฃ !

์ž, ์ด๋ ‡๊ฒŒ C#์—์„œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์„ธ ๊ฐ€์ง€ ๊ธฐ๋ณธ ์ „๋žต์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ์–ด๋–ค๊ฐ€์š”? ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์ฃ ? ๐Ÿ˜‰

ํ•˜์ง€๋งŒ ์ด๊ฒŒ ๋์ด ์•„๋‹ˆ์—์š”! ์‹ค์ œ๋กœ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ์ด๋Ÿฐ ๊ธฐ๋ณธ ์ „๋žต์„ ๋ฐ”ํƒ•์œผ๋กœ ๋” ๋ณต์žกํ•˜๊ณ  ์„ธ๋ฐ€ํ•œ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ด์š”. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” ์ข€ ๋” ์‹ฌํ™”๋œ ๋‚ด์šฉ์„ ๋‹ค๋ค„๋ณผ ๊ฑฐ์˜ˆ์š”. ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ๊ณ ๊ณ ์”ฝ~! ๐Ÿš€

3. C#์—์„œ์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์‹ฌํ™” ๊ตฌํ˜„ ์ „๋žต ๐Ÿง 

์ž, ์ด์ œ ์ข€ ๋” ๊นŠ์ด ๋“ค์–ด๊ฐ€๋ณผ ์‹œ๊ฐ„์ด์—์š”! ์—ฌ๋Ÿฌ๋ถ„, ์ค€๋น„๋˜์…จ๋‚˜์š”? ๐Ÿค“

์•ž์„œ ์šฐ๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์ธ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ํ•˜์ง€๋งŒ ์‹ค์ œ ๊ธฐ์—…์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ๋•Œ๋Š” ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ด์š”. ๋” ๋ณต์žกํ•˜๊ณ  ์„ธ๋ฐ€ํ•œ ์ „๋žต์ด ํ•„์š”ํ•˜์ฃ . ๊ทธ๋Ÿผ ์–ด๋–ค ์ „๋žต๋“ค์ด ์žˆ๋Š”์ง€ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณผ๊นŒ์š”?

3.1 ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ™œ์šฉํ•œ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ

์˜์กด์„ฑ ์ฃผ์ž…(Dependency Injection)์€ C#์—์„œ ์•„์ฃผ ์ค‘์š”ํ•œ ๊ฐœ๋…์ด์—์š”. ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„์—๋„ ์ด ๊ฐœ๋…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

์˜์กด์„ฑ ์ฃผ์ž…์ด๋ž€? ํด๋ž˜์Šค ๊ฐ„์˜ ์˜์กด ๊ด€๊ณ„๋ฅผ ์™ธ๋ถ€์—์„œ ๊ฒฐ์ •ํ•˜๊ณ  ์ฃผ์ž…ํ•˜๋Š” ๋””์ž์ธ ํŒจํ„ด์ด์—์š”. ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์„ ๋†’์—ฌ์ฃผ์ฃ .

ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ค ์žฅ์ ์ด ์žˆ์„๊นŒ์š”?

  • ์ฝ”๋“œ์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์–ด์š”.
  • ํ…Œ๋„ŒํŠธ๋ณ„๋กœ ๋‹ค๋ฅธ ๊ตฌํ˜„์„ ์‰ฝ๊ฒŒ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.

์ž, ๊ทธ๋Ÿผ ์ฝ”๋“œ๋กœ ํ•œ๋ฒˆ ์‚ดํŽด๋ณผ๊นŒ์š”?


public interface ITenantContext
{
    string TenantId { get; }
}

public class TenantContext : ITenantContext
{
    public string TenantId { get; }

    public TenantContext(IHttpContextAccessor httpContextAccessor)
    {
        // HTTP ์š”์ฒญ ํ—ค๋”์—์„œ ํ…Œ๋„ŒํŠธ ID๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๊ณ  ๊ฐ€์ •
        TenantId = httpContextAccessor.HttpContext?.Request.Headers["X-TenantId"].FirstOrDefault();
    }
}

public class UserService
{
    private readonly ITenantContext _tenantContext;
    private readonly IUserRepository _userRepository;

    public UserService(ITenantContext tenantContext, IUserRepository userRepository)
    {
        _tenantContext = tenantContext;
        _userRepository = userRepository;
    }

    public IEnumerable<user> GetUsers()
    {
        return _userRepository.GetUsers(_tenantContext.TenantId);
    }
}

// Startup.cs์—์„œ์˜ ์„œ๋น„์Šค ๋“ฑ๋ก
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    services.AddScoped<itenantcontext tenantcontext>();
    services.AddScoped<iuserrepository userrepository>();
    services.AddScoped<userservice>();
}
</userservice></iuserrepository></itenantcontext></user>

์ด ์ฝ”๋“œ์—์„œ๋Š” ITenantContext๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ํ…Œ๋„ŒํŠธ ์ •๋ณด๋ฅผ ์บก์Šํ™”ํ•˜๊ณ , ์ด๋ฅผ UserService์— ์ฃผ์ž…ํ•˜๊ณ  ์žˆ์–ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด UserService๋Š” ํ…Œ๋„ŒํŠธ ์ •๋ณด๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ณ , ํ…Œ์ŠคํŠธํ•  ๋•Œ๋„ ๊ฐ€์งœ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์–ด์š”.

3.2 ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ด์šฉํ•œ ํ…Œ๋„ŒํŠธ ์‹๋ณ„

ASP.NET Core์—์„œ๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•ด HTTP ์š”์ฒญ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ฅผ ํ™œ์šฉํ•ด ํ…Œ๋„ŒํŠธ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ฃ .

๋ฏธ๋“ค์›จ์–ด๋ž€? HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์˜ ๊ตฌ์„ฑ ์š”์†Œ์˜ˆ์š”. ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜, ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์ฃ .

ํ…Œ๋„ŒํŠธ ์‹๋ณ„ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”?


public class TenantIdentificationMiddleware
{
    private readonly RequestDelegate _next;

    public TenantIdentificationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, ITenantContext tenantContext)
    {
        //  ๋„ค, ๊ณ„์†ํ•ด์„œ TenantIdentificationMiddleware ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

<pre><code>
public class TenantIdentificationMiddleware
{
    private readonly RequestDelegate _next;

    public TenantIdentificationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, ITenantContext tenantContext)
    {
        // ํ—ค๋”์—์„œ ํ…Œ๋„ŒํŠธ ID๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        var tenantId = context.Request.Headers["X-TenantId"].FirstOrDefault();

        if (string.IsNullOrEmpty(tenantId))
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Tenant ID is missing");
            return;
        }

        // ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ์— ํ…Œ๋„ŒํŠธ ID๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        ((TenantContext)tenantContext).SetTenantId(tenantId);

        // ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ฒ˜๋ฆฌ๋ฅผ ๋„˜๊น๋‹ˆ๋‹ค.
        await _next(context);
    }
}

// Startup.cs์— ๋ฏธ๋“ค์›จ์–ด ๋“ฑ๋ก
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด ์„ค์ •...

    app.UseMiddleware<tenantidentificationmiddleware>();

    // ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด ์„ค์ •...
}
</tenantidentificationmiddleware>

์ด ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ํ…Œ๋„ŒํŠธ ID๋ฅผ ํ™•์ธํ•˜๊ณ , ์ด๋ฅผ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ์— ์„ค์ •ํ•ด์š”. ํ…Œ๋„ŒํŠธ ID๊ฐ€ ์—†์œผ๋ฉด ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•˜์ฃ . ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๋ถ€๋ถ„์—์„œ ์ผ๊ด€๋œ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

3.3 Entity Framework Core๋ฅผ ์ด์šฉํ•œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„

Entity Framework Core๋Š” C#์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ORM(Object-Relational Mapping) ๋„๊ตฌ์˜ˆ์š”. EF Core๋ฅผ ์‚ฌ์šฉํ•ด ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊นŒ์š”?


public class ApplicationDbContext : DbContext
{
    private readonly ITenantContext _tenantContext;

    public ApplicationDbContext(DbContextOptions<applicationdbcontext> options, ITenantContext tenantContext)
        : base(options)
    {
        _tenantContext = tenantContext;
    }

    public DbSet<user> Users { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ์ „์—ญ ์ฟผ๋ฆฌ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            var entityTypeBuilder = modelBuilder.Entity(entityType.ClrType);
            if (entityType.ClrType.GetProperty("TenantId") != null)
            {
                var parameter = Expression.Parameter(entityType.ClrType, "e");
                var body = Expression.Equal(
                    Expression.Property(parameter, "TenantId"),
                    Expression.Constant(_tenantContext.TenantId)
                );
                var lambda = Expression.Lambda(body, parameter);

                entityTypeBuilder.HasQueryFilter(lambda);
            }
        }
    }

    public override int SaveChanges()
    {
        PerformTenantOperations();
        return base.SaveChanges();
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        PerformTenantOperations();
        return base.SaveChangesAsync(cancellationToken);
    }

    private void PerformTenantOperations()
    {
        foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added))
        {
            if (entry.Entity is ITenantEntity tenantEntity)
            {
                tenantEntity.TenantId = _tenantContext.TenantId;
            }
        }
    }
}

public interface ITenantEntity
{
    string TenantId { get; set; }
}

public class User : ITenantEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string TenantId { get; set; }
}
</int></user></applicationdbcontext>

์ด ์ฝ”๋“œ์—์„œ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”:

  1. ์ „์—ญ ์ฟผ๋ฆฌ ํ•„ํ„ฐ: OnModelCreating ๋ฉ”์„œ๋“œ์—์„œ ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ํ…Œ๋„ŒํŠธ ID ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋“  ์ฟผ๋ฆฌ์— ์ž๋™์œผ๋กœ ํ…Œ๋„ŒํŠธ ID ์กฐ๊ฑด์ด ์ถ”๊ฐ€๋ผ์š”.
  2. ์ž๋™ ํ…Œ๋„ŒํŠธ ID ์„ค์ •: SaveChanges์™€ SaveChangesAsync ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์„œ ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๋Š” ์—”ํ‹ฐํ‹ฐ์— ์ž๋™์œผ๋กœ ํ…Œ๋„ŒํŠธ ID๋ฅผ ์„ค์ •ํ•ด์š”.
  3. ITenantEntity ์ธํ„ฐํŽ˜์ด์Šค: ํ…Œ๋„ŒํŠธ ID๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๋“ค์ด ๊ตฌํ˜„ํ•ด์•ผ ํ•  ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•ด์š”.

3.4 ํ…Œ๋„ŒํŠธ๋ณ„ ์„ค์ • ๊ด€๋ฆฌ

๊ฐ ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๋‹ค๋ฅธ ์„ค์ •์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์š”. ์ด๋ฅผ ์œ„ํ•œ ์„ค์ • ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”?


public interface ITenantSettings
{
    string GetSetting(string key);
}

public class TenantSettings : ITenantSettings
{
    private readonly ITenantContext _tenantContext;
    private readonly IConfiguration _configuration;

    public TenantSettings(ITenantContext tenantContext, IConfiguration configuration)
    {
        _tenantContext = tenantContext;
        _configuration = configuration;
    }

    public string GetSetting(string key)
    {
        return _configuration[$"TenantSettings:{_tenantContext.TenantId}:{key}"]
            ?? _configuration[$"DefaultSettings:{key}"];
    }
}

// appsettings.json
{
  "TenantSettings": {
    "tenant1": {
      "Theme": "Dark",
      "MaxUsers": "100"
    },
    "tenant2": {
      "Theme": "Light",
      "MaxUsers": "50"
    }
  },
  "DefaultSettings": {
    "Theme": "Light",
    "MaxUsers": "10"
  }
}

์ด ์ฝ”๋“œ๋Š” ๊ฐ ํ…Œ๋„ŒํŠธ๋ณ„๋กœ ๋‹ค๋ฅธ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”. ํ…Œ๋„ŒํŠธ๋ณ„ ์„ค์ •์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ ์„ค์ •์„ ์‚ฌ์šฉํ•˜์ฃ .

3.5 ํ…Œ๋„ŒํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ ๊ฐ•ํ™”

๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ๋Š” ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์˜ˆ์š”. EF Core์˜ ๊ธฐ๋Šฅ์„ ์ข€ ๋” ํ™œ์šฉํ•ด ๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ๋ฅผ ๊ฐ•ํ™”ํ•ด๋ณผ๊นŒ์š”?


public class TenantEntitySaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly ITenantContext _tenantContext;

    public TenantEntitySaveChangesInterceptor(ITenantContext tenantContext)
    {
        _tenantContext = tenantContext;
    }

    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
        var context = eventData.Context;
        if (context == null) return result;

        var entries = context.ChangeTracker.Entries<itenantentity>().ToList();

        foreach (var entry in entries)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    entry.Entity.TenantId = _tenantContext.TenantId;
                    break;
                case EntityState.Modified:
                    if (entry.Entity.TenantId != _tenantContext.TenantId)
                        throw new UnauthorizedAccessException("You cannot modify data from another tenant.");
                    break;
                case EntityState.Deleted:
                    if (entry.Entity.TenantId != _tenantContext.TenantId)
                        throw new UnauthorizedAccessException("You cannot delete data from another tenant.");
                    break;
            }
        }

        return result;
    }
}

// Startup.cs์—์„œ ์ธํ„ฐ์…‰ํ„ฐ ๋“ฑ๋ก
services.AddDbContext<applicationdbcontext>((sp, options) =>
{
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    options.AddInterceptors(sp.GetRequiredService<tenantentitysavechangesinterceptor>());
});
</tenantentitysavechangesinterceptor></applicationdbcontext></itenantentity></int></int>

์ด ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ €์žฅ๋˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋˜์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์š”:

  1. ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๋Š” ์—”ํ‹ฐํ‹ฐ์— ํ˜„์žฌ ํ…Œ๋„ŒํŠธ ID๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ˆ˜์ •๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ํ˜„์žฌ ํ…Œ๋„ŒํŠธ์˜ ๊ฒƒ์ธ์ง€ ํ™•์ธํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‹ค์ˆ˜๋กœ ๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๋งˆ๋ฌด๋ฆฌ

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ C#์—์„œ์˜ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„์— ๋Œ€ํ•ด ์‹ฌ๋„ ์žˆ๊ฒŒ ์•Œ์•„๋ดค์–ด์š”. ์–ด๋– ์…จ๋‚˜์š”? ์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์˜€์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๋‹ˆ ๊ทธ๋ ‡๊ฒŒ ์–ด๋ ต์ง€๋งŒ์€ ์•Š์ฃ ? ๐Ÿ˜Š

๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ตฌํ˜„์€ ๋‹จ์ˆœํžˆ ์ฝ”๋“œ ๋ช‡ ์ค„๋กœ ๋๋‚˜๋Š” ๊ฒŒ ์•„๋‹ˆ์—์š”. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์ค‘์š”ํ•œ ๊ฒฐ์ •์ด์ฃ . ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ธฐ์ˆ ๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด, ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ์•ˆ์ „ํ•œ ๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”.

์—ฌ๋Ÿฌ๋ถ„๋„ ์ด์ œ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ์˜ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋œ ๊ฒƒ ๊ฐ™์€๋ฐ์š”? ๐Ÿ˜Ž ์ด ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฉ‹์ง„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ํ™”์ดํŒ…! ๐Ÿš€