๐ข ๊ธฐ์ ์ฉ C# ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฉํฐํ ๋์ ๊ตฌํ ์ ๋ต ๐

์๋ ํ์ธ์, ์ฌ๋ฌ๋ถ! ์ค๋์ ์ข ํน๋ณํ ์ฃผ์ ๋ก ์ฐพ์์์ด์. ๋ฐ๋ก "๊ธฐ์ ์ฉ C# ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฉํฐํ ๋์ ๊ตฌํ ์ ๋ต"์ ๋ํด ์๊ธฐํด๋ณผ ๊ฑฐ์์. ์ด๋ง์ด๋งํ๊ฒ ๋ณต์กํด ๋ณด์ด๋ ์ด ์ฃผ์ , ๊ณผ์ฐ ์ฐ๋ฆฌ๊ฐ ์ฝ๊ฒ ์ดํดํ ์ ์์๊น์? ๋น์ฐํ์ฃ ! ์ ๊ฐ ์ฌ๋ฌ๋ถ์ ๋ ๋ ํ ๊ฐ์ด๋๊ฐ ๋์ด ๋๋ฆด๊ฒ์. ๐
์ฐ์ , ์ด ์ฃผ์ ๊ฐ ์ ์ค์ํ์ง ์์๋์? ์์ฆ IT ์ ๊ณ์์๋ "๋ฉํฐํ ๋์"๋ผ๋ ๊ฐ๋ ์ด ์ ๋ง ํซํด์! ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ํ ๊ณณ์์ ๊ฑฐ๋ํ๋ฏ์ด, ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ฌ๋ฌ ๊ณ ๊ฐ(ํ ๋ํธ)๋ฅผ ์๋น์คํ๋ ๊ฑฐ์ฃ . ์ด๊ฒ ๋ฐ๋ก ๋ฉํฐํ ๋์์ ํต์ฌ์ด์์!
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์ํด๋ณผ๊น์? ์ค๋น๋์ จ๋์? ๊ทธ๋ผ ๊ณ ๊ณ ์ฝ~! ๐
1. ๋ฉํฐํ ๋์๋ ๋ญ์ผ? ๐ค
๋ฉํฐํ ๋์... ๋ญ๊ฐ ์ด๋ ค์ ๋ณด์ด๋ ์ด ๋จ์ด, ์ฌ์ค ๊ทธ๋ ๊ฒ ๋ณต์กํ ๊ฐ๋ ์ด ์๋์์! ์ฝ๊ฒ ์ค๋ช ํด๋๋ฆด๊ฒ์.
๋ฉํฐํ ๋์(Multi-tenancy)๋? ํ๋์ ์ํํธ์จ์ด ์ธ์คํด์ค๊ฐ ์ฌ๋ฌ ๊ณ ๊ฐ(ํ ๋ํธ)์ ๋์์ ์๋น์คํ๋ ์ํคํ ์ฒ๋ฅผ ๋งํด์.
์... ์์ง๋ ์ข ์ด๋ ต๋์? ๊ทธ๋ผ ๋ ์ฝ๊ฒ ์ค๋ช ํด๋ณผ๊ฒ์! ๐
์ฌ๋ฌ๋ถ, ์ํํธ ์๊ฐํด๋ณด์ธ์. ํ ๊ฑด๋ฌผ์ ์ฌ๋ฌ ๊ฐ๊ตฌ๊ฐ ์ด๊ณ ์์ฃ ? ๊ฐ ๊ฐ๊ตฌ๋ ์๊ธฐ๋ง์ ๊ณต๊ฐ์ด ์๊ณ , ๊ทธ ์์์ ๋ ๋ฆฝ์ ์ผ๋ก ์ํํด์. ํ์ง๋ง ๊ฑด๋ฌผ ์์ฒด๋ ํ๋๊ณ , ์๋ฆฌ๋ฒ ์ดํฐ๋ ์ฃผ์ฐจ์ฅ ๊ฐ์ ๊ณต์ฉ ์์ค์ ํจ๊ป ์ฌ์ฉํ์ฃ .
๋ฉํฐํ ๋์๋ ์ด์ ๋น์ทํด์! ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ (์ํํธ ๊ฑด๋ฌผ)์ด ์ฌ๋ฌ ๊ณ ๊ฐ(์ ์ฃผ ๊ฐ๊ตฌ)์ ๋์์ ์๋น์คํ๋ ๊ฑฐ์์. ๊ฐ ๊ณ ๊ฐ์ ์์ ๋ง์ ๋ ๋ฆฝ๋ ๊ณต๊ฐ(๋ฐ์ดํฐ, ์ค์ ๋ฑ)์ ๊ฐ์ง์ง๋ง, ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๋๋ ์๋ฒ ์์์ ๊ณต์ ํ๋ ๊ฑฐ์ฃ .
์ดํด๊ฐ ์ข ๋์ จ๋์? ๐ ๋ฉํฐํ ๋์์ ์ฅ์ ์ ๋ญ๊น์?
- ๋น์ฉ ์ ๊ฐ: ํ๋์ ์ธ์คํด์ค๋ก ์ฌ๋ฌ ๊ณ ๊ฐ์ ์๋น์คํ๋๊น ์๋ฒ ๋น์ฉ์ด ์ค์ด๋ค์ด์.
- ํจ์จ์ ์ธ ๋ฆฌ์์ค ๊ด๋ฆฌ: ์์์ ๊ณต์ ํ๋๊น ๋ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ด์.
- ์ ์ง๋ณด์ ํธ์์ฑ: ํ๋์ ์ฝ๋๋ฒ ์ด์ค๋ง ๊ด๋ฆฌํ๋ฉด ๋๋๊น ์ ๋ฐ์ดํธ๋ ๋ฒ๊ทธ ์์ ์ด ์ฌ์์ ธ์.
- ํ์ฅ์ฑ: ์๋ก์ด ๊ณ ๊ฐ์ ์ถ๊ฐํ๊ธฐ๊ฐ ํจ์ฌ ์ฌ์์ ธ์.
ํ์ง๋ง ์ฅ์ ๋ง ์๋ ๊ฑด ์๋์์. ๋จ์ ๋ ์์ฃ !
- ๋ณด์: ์ฌ๋ฌ ๊ณ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ํ ๊ณณ์ ์์ผ๋๊น ๋ฐ์ดํฐ ๋ถ๋ฆฌ์ ๋ณด์์ ๋ ์ ๊ฒฝ ์จ์ผ ํด์.
- ๋ณต์ก์ฑ: ์ค๊ณ์ ๊ตฌํ์ด ๋จ์ผ ํ ๋ํธ ์์คํ ๋ณด๋ค ๋ณต์กํด์ง ์ ์์ด์.
- ์ฑ๋ฅ: ํ ํ ๋ํธ์ ๊ณผ๋ํ ์ฌ์ฉ์ด ๋ค๋ฅธ ํ ๋ํธ์๊ฒ ์ํฅ์ ์ค ์ ์์ด์.
์, ์ด์ ๋ฉํฐํ ๋์๊ฐ ๋ญ์ง ๋์ถฉ ๊ฐ์ด ์ค์์ฃ ? ๐ ๊ทธ๋ผ ์ด์ C#์์ ์ด๊ฑธ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณผ๊น์? ํฅ๋ฏธ์ง์งํ ์ฌ์ ์ด ๊ธฐ๋ค๋ฆฌ๊ณ ์์ด์! ๋ค์ ์น์ ์ผ๋ก ๊ณ ๊ณ ์ฝ~! ๐
2. C#์์์ ๋ฉํฐํ ๋์ ๊ตฌํ ๊ธฐ๋ณธ ์ ๋ต ๐ ๏ธ
์, ์ด์ C#์์ ๋ฉํฐํ ๋์๋ฅผ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณผ ์ฐจ๋ก์์. ์ฌ๋ฌ๋ถ, ์ค๋น๋์ จ๋์? ๐
C#์์ ๋ฉํฐํ ๋์๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ธ ๊ฐ์ง๋ก ๋๋ ์ ์์ด์.
C#์์์ ๋ฉํฐํ ๋์ ๊ตฌํ ๋ฐฉ๋ฒ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ค์ ๋ถ๋ฆฌ
- ์คํค๋ง ์์ค์ ๋ถ๋ฆฌ
- ํ ์์ค์ ๋ถ๋ฆฌ
๊ฐ๊ฐ์ ๋ฐฉ๋ฒ์ ๋ํด ์์ธํ ์์๋ณผ๊น์? ๊ณ ๊ณ ์ฝ~! ๐
2.1 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ค์ ๋ถ๋ฆฌ
์ด ๋ฐฉ๋ฒ์ ๊ฐ ํ ๋ํธ๋ง๋ค ๋ณ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฑฐ์์. ๋ง์น ๊ฐ ๊ฐ๊ตฌ๋ง๋ค ๋ณ๋์ ์ฐฝ๊ณ ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ฒ๋ผ์!
์ด ๋ฐฉ๋ฒ์ ์ฅ๋จ์ ์ ์ดํด๋ณผ๊น์?
- ์ฅ์ :
- ๋ฐ์ดํฐ ๋ถ๋ฆฌ๊ฐ ์๋ฒฝํด์ ๋ณด์์ฑ์ด ๋์์.
- ๊ฐ ํ ๋ํธ๋ง๋ค ๋ง์ถค ์ค์ ์ด ์ฌ์์.
- ํ ํ ๋ํธ์ ๋ฌธ์ ๊ฐ ๋ค๋ฅธ ํ ๋ํธ์๊ฒ ์ํฅ์ ์ฃผ์ง ์์์.
- ๋จ์ :
- ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๋ง์์ง๋ฉด ๊ด๋ฆฌ๊ฐ ๋ณต์กํด์ง ์ ์์ด์.
- ๋ฆฌ์์ค ์ฌ์ฉ์ด ๋นํจ์จ์ ์ผ ์ ์์ด์.
- ํ ๋ํธ ์๊ฐ ๋ง์์ง๋ฉด ๋น์ฉ์ด ์ฆ๊ฐํ ์ ์์ด์.
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 ์คํค๋ง ์์ค์ ๋ถ๋ฆฌ
์ด ๋ฐฉ๋ฒ์ ํ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์์ ๊ฐ ํ ๋ํธ๋ง๋ค ๋ณ๋์ ์คํค๋ง๋ฅผ ์ฌ์ฉํด์. ์ํํธ๋ก ์น๋ฉด ๊ฐ ๊ฐ๊ตฌ๋ง๋ค ๋ค๋ฅธ ์ธต์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋น์ทํ์ฃ !
์ด ๋ฐฉ๋ฒ์ ์ฅ๋จ์ ์ ๋ญ๊น์?
- ์ฅ์ :
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ค์ ๋ถ๋ฆฌ๋ณด๋ค ๋ฆฌ์์ค๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ด์.
- ํ ๋ํธ ๊ฐ ๋ฐ์ดํฐ ๋ถ๋ฆฌ๊ฐ ๋ช ํํด์.
- ๋ฐฑ์ ๊ณผ ๋ณต์์ด ๋น๊ต์ ์ฌ์์.
- ๋จ์ :
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ค์ ๋ถ๋ฆฌ๋งํผ ์๋ฒฝํ ๊ฒฉ๋ฆฌ๋ ์๋์์.
- ์คํค๋ง ๊ด๋ฆฌ๊ฐ ๋ณต์กํด์ง ์ ์์ด์.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฒด์ ์ํฅ์ ์ฃผ๋ ์์ ์ ์ฃผ์๊ฐ ํ์ํด์.
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 ํํฐ๋ง์ด ํ์ํด์ ์ค์ํ ๊ฐ๋ฅ์ฑ์ด ์์ด์.
- ํ ๋ํธ๋ณ ์คํค๋ง ๋ณ๊ฒฝ์ด ์ด๋ ค์์.
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>
์ด ์ฝ๋์์๋ ๋ช ๊ฐ์ง ์ค์ํ ๊ธฐ๋ฒ์ ์ฌ์ฉํ๊ณ ์์ด์:
- ์ ์ญ ์ฟผ๋ฆฌ ํํฐ:
OnModelCreating
๋ฉ์๋์์ ๋ชจ๋ ์ํฐํฐ์ ๋ํด ํ ๋ํธ ID ํํฐ๋ฅผ ์ ์ฉํด์. ์ด๋ ๊ฒ ํ๋ฉด ๋ชจ๋ ์ฟผ๋ฆฌ์ ์๋์ผ๋ก ํ ๋ํธ ID ์กฐ๊ฑด์ด ์ถ๊ฐ๋ผ์. - ์๋ ํ
๋ํธ ID ์ค์ :
SaveChanges
์SaveChangesAsync
๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํด์ ์๋ก ์ถ๊ฐ๋๋ ์ํฐํฐ์ ์๋์ผ๋ก ํ ๋ํธ ID๋ฅผ ์ค์ ํด์. - 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>
์ด ์ธํฐ์ ํฐ๋ ์ํฐํฐ๊ฐ ์ ์ฅ๋๊ธฐ ์ ์ ํธ์ถ๋์ด ๋ค์๊ณผ ๊ฐ์ ์์ ์ ์ํํด์:
- ์๋ก ์ถ๊ฐ๋๋ ์ํฐํฐ์ ํ์ฌ ํ ๋ํธ ID๋ฅผ ์ค์ ํฉ๋๋ค.
- ์์ ๋๊ฑฐ๋ ์ญ์ ๋๋ ์ํฐํฐ๊ฐ ํ์ฌ ํ ๋ํธ์ ๊ฒ์ธ์ง ํ์ธํ๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์ค์๋ก ๋ค๋ฅธ ํ ๋ํธ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฑฐ๋ ์ญ์ ํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ด์.
๋ง๋ฌด๋ฆฌ
์, ์ฌ๊ธฐ๊น์ง C#์์์ ๋ฉํฐํ ๋์ ๊ตฌํ์ ๋ํด ์ฌ๋ ์๊ฒ ์์๋ดค์ด์. ์ด๋ ์ จ๋์? ์ฒ์์๋ ๋ณต์กํด ๋ณด์์ง๋ง, ํ๋์ฉ ์ดํด๋ณด๋ ๊ทธ๋ ๊ฒ ์ด๋ ต์ง๋ง์ ์์ฃ ? ๐
๋ฉํฐํ ๋์ ๊ตฌํ์ ๋จ์ํ ์ฝ๋ ๋ช ์ค๋ก ๋๋๋ ๊ฒ ์๋์์. ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ฒด ์ํคํ ์ฒ์ ์ํฅ์ ๋ฏธ์น๋ ์ค์ํ ๊ฒฐ์ ์ด์ฃ . ํ์ง๋ง ์ด๋ฐ ๊ธฐ์ ๋ค์ ์ ํ์ฉํ๋ฉด, ํ์ฅ ๊ฐ๋ฅํ๊ณ ์์ ํ ๋ฉํฐํ ๋ํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ด์.
์ฌ๋ฌ๋ถ๋ ์ด์ ๋ฉํฐํ ๋์์ ์ ๋ฌธ๊ฐ๊ฐ ๋ ๊ฒ ๊ฐ์๋ฐ์? ๐ ์ด ์ง์์ ๋ฐํ์ผ๋ก ๋ฉ์ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด์ธ์. ํ์ดํ ! ๐
- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ