초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Amazon.DynamoDBv2.DocumentModel;
using Newtonsoft.Json.Linq;
namespace ServerBase;
public abstract partial class AttribBase
{
// AttribType은 DynamoDb Document 정보를 Read시 식별키로 사용되기 때문에 고유하게 정의되어야 한다 !!! - kangms
[JsonProperty("attrib_type")]
public readonly string AttribType;
private readonly bool m_is_save_to_json_in_db = false;
public AttribBase(string attribType, bool isSaveToJsonInDb = true)
{
AttribType = attribType;
m_is_save_to_json_in_db = isSaveToJsonInDb;
}
public bool isSaveToJsonInDb() => m_is_save_to_json_in_db;
public virtual string toJsonString()
{
return JsonConvert.SerializeObject(this);
}
public virtual Amazon.DynamoDBv2.DocumentModel.Document toDocument()
{
return DynamoDbClientHelper.classToDynamoDbDocument(this);
}
public virtual string toBasicString()
{
return $"AttribType:{AttribType}, {toJsonString()}";
}
}
public partial class AttribWrapper<TAttrib> : IAttribWrapper
where TAttrib : AttribBase, new()
{
private TAttrib m_attrib = new();
public AttribWrapper()
{}
public bool onCopyFromDynamoDbEntry(Amazon.DynamoDBv2.DocumentModel.Document doc)
{
if(false == doc.fillupAttribObject<TAttrib>(out var fillup_attrib))
{
return false;
}
if (fillup_attrib == null)
{
return false;
}
m_attrib = fillup_attrib;
return true;
}
public bool onCopyToDynamoDbEntry(ref Amazon.DynamoDBv2.DocumentModel.Document doc)
{
return doc.setAttribObject<TAttrib>(m_attrib);
}
public bool onFillupJsonStringToJObject(ref JObject jobject)
{
return jobject.setAttribObjectWithJsonString<TAttrib>(m_attrib);
}
}

View File

@@ -0,0 +1,275 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using ServerCore;
using ServerBase;
using DYNAMO_DB_TABLE_NAME = System.String;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
namespace ServerBase;
public partial class DynamoDbClient : DynamoDbConnectorBase, IModule, IInitializer
{
public const string TTL_NAME = "TTL";
public const string SK_EMPTY = "empty";
public const string PK_GLOBAL = "global";
private readonly ModuleContext? m_module_context;
public DynamoDbClient()
: base()
{ }
public DynamoDbClient(ModuleContext moduleContext)
: base()
{
m_module_context = moduleContext;
}
public async Task<Result> onInit()
{
var err_msg = string.Empty;
var result = new Result();
var module_context = getModuleContext();
var config_param = module_context.getConfigParam() as ConfigParam;
NullReferenceCheckHelper.throwIfNull(config_param, () => $"config_param is null !!!");
result = connectToDb(config_param);
if (result.isFail())
{
err_msg = $"Failed to create DB Table !!! - {toBasicString()}";
Log.getLogger().fatal(result.toBasicString());
return result;
}
if (false == await createDBIfNotExists(false))
{
err_msg = $"Failed to createDBIfNotExists() !!! - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbTableCreateFailed, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
return result;
}
public async Task<Result> startModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public async Task<Result> stopModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public Result connectToDb(ConfigParam configParam)
{
var result = new Result();
var err_msg = string.Empty;
(var error_code, var to_load_table_names) = ServerConfigHelper.getDynamoDbTableNamesWithServiceType(configParam.ServiceType);
if (error_code.isFail())
{
err_msg = $"Failed to DynamoDbClient.getDynamoDbTableNameWithServiceType() !!! : {configParam.toBasicString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
return connectToDb( to_load_table_names
, configParam.IsLocalDynamoDB, configParam.UrlOfDynamoDb
, configParam.AccessKeyOfAws, configParam.SecretKeyOfAws, configParam.RegionOfAws );
}
public Result connectToDb( ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME> tableNames, bool localDynamoDb
, string dynamodbUrl
, string awsAccessKey, string awsSecretKey
, string awsRegion = "" )
{
var result = new Result();
var err_msg = string.Empty;
foreach( var each in tableNames )
{
var table_name = each.Key;
var table_full_name = each.Value;
if(false == addTableFullName(table_name, table_full_name))
{
err_msg = $"Failed to addTableFullName() !!! : tableName:{table_name}, tableFullName:{table_full_name} - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbTableNameDuplicated, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
}
var db_config = new AmazonDynamoDBConfig();
db_config.BufferSize = 4 * 1024 * 1024; // 4 MB
db_config.RetryMode = RequestRetryMode.Standard;
db_config.DisableLogging = false; // Logging is disabled by default. - Set to true to enable request-level logging.
db_config.ThrottleRetries = false; // Throttled requests are not automatically retried. - Set to false to automatically retry throttled requests.
if (localDynamoDb)
{
// DynamoDB-Local is running, so create a client
Log.getLogger().info($"Setting up a DynamoDB-Local client (DynamoDB Local seems to be running)");
db_config.ServiceURL = dynamodbUrl;
if (false == base.connectToDb(awsAccessKey, awsSecretKey, db_config))
{
err_msg = $"Failed to connect a DynamoDB-Local client !!! : {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
else
{
Log.getLogger().info($"Setting up a DynamoDB-Remote client : TargetRegion:{awsRegion}");
try
{
db_config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(awsRegion);
if (false == base.connectToDb(awsAccessKey, awsSecretKey, db_config))
{
err_msg = $"Failed to connect a DynamoDB-Remote client !!! : {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
catch (Exception e)
{
err_msg = $"Failed to connect a DynamoDB-Remote client !!! : Exception:{e}, {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
}
return result;
}
public Result connectToDb( string tableName, bool localDynamoDB, string dynamodbUrl
, string awsAccessKey, string awsSecretKey, string awsRegion = "")
{
var table_names = new ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME>();
table_names[tableName] = tableName;
return connectToDb( table_names, localDynamoDB, dynamodbUrl
, awsAccessKey, awsSecretKey, awsRegion );
}
public async Task<bool> createDBIfNotExists(bool resetTable)
{
var err_msg = string.Empty;
var db_client = getDbClient();
if (db_client == null)
{
err_msg = $"Not created DynamoDbClient !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
var loaded_table_full_names = getTableNames().Values.ToList();
if (resetTable == true)
{
if(false == await deleteTables(loaded_table_full_names))
{
err_msg = $"Failed to DynamoDbClient.deleteTables() !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
List<AttributeDefinition> tableAttributes = new List<AttributeDefinition>();
tableAttributes.Add(new AttributeDefinition(PrimaryKey.PK_Define, ScalarAttributeType.S));
tableAttributes.Add(new AttributeDefinition(PrimaryKey.SK_Define, ScalarAttributeType.S));
List<KeySchemaElement> tableKeySchema = new List<KeySchemaElement>();
tableKeySchema.Add(new KeySchemaElement(PrimaryKey.PK_Define, KeyType.HASH));
tableKeySchema.Add(new KeySchemaElement(PrimaryKey.SK_Define, KeyType.RANGE));
foreach (var table_full_name in loaded_table_full_names)
{
if (await isExistTable(table_full_name) == false)
{
var ttl_update = new UpdateTimeToLiveRequest
{
TableName = table_full_name,
TimeToLiveSpecification = new TimeToLiveSpecification
{
AttributeName = TTL_NAME, // TTL로 사용할 속성 이름
Enabled = true // TTL 사용 여부
}
};
if(false == await createTable( db_client, table_full_name, ttl_update
, tableAttributes, tableKeySchema ))
{
err_msg = $"Failed to DynamoDbClient.addTable() !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
if(false == addTable(table_full_name, Table.LoadTable(db_client, table_full_name)))
{
err_msg = $"Failed to DynamoDbClient.addTable() !!! : tableName:{table_full_name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
DynamoDbQueryOperationOptionConfig.configure();
return true;
}
public Table getTableByDoc<TDoc>()
where TDoc : DynamoDbDocBase, new()
{
return getTableByName((new TDoc()).TableName);
}
public Table getTableByDoc<TDoc>(TDoc doc)
where TDoc : DynamoDbDocBase
{
return getTableByName(doc.TableName);
}
public ModuleContext getModuleContext()
{
NullReferenceCheckHelper.throwIfNull(m_module_context, () => $"m_module_context is null !!!");
return m_module_context;
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
using LINK_PKSK = System.String;
namespace ServerBase;
/*=============================================================================================
DynamoDb 정보 관리 흐름도
# 읽기는 Document Only, 쓰기는 Document + ItemRequest 로 처리는 하는 것이 적절하다 !!!
1. Document Only 시퀀스
* 읽기 => 쓰기
DB => Document => Attrib => Doc => EntityAttribute => Doc => Attrib => Document => DB
2. ItemRequest Only 시퀀스
* 읽기 => 쓰기
DB => ItemRequest => EntityAttribute => ItemRequest => DB
3. Document + ItemRequest 시퀀스
* 읽기 => 쓰기
DB => ItemRequest => Document => Attrib => Doc => Attrib => Document => ItemRequest => DB
- kangms
//===========================================================================================*/
//=============================================================================================
// DynamoDB의 쿼리의 종류를 정의 한다.
//
// - kangms
//=============================================================================================
public enum QueryType
{
None = 0,
// 일반 쿼리
Select, // 조회 한다.
Insert, // 존재하지 않은 경우만 추가 한다.
Update, // 존재하는 경우만 업데이트 한다.
Upsert, // 존재하지 않으면 추가하고, 있으면 업데이트 한다.
Delete, // 존재하는 경우 삭제 한다.
// 특수 쿼리
ExistsDelete, // 존재하지 않을 경우, 삭제되기 전 모든 속성을 반환 한다.
}
//=============================================================================================
// DynamoDB의 PrimaryKey를 설정하는 클래스 이다.
//
// - kangms
//=============================================================================================
public class PrimaryKey
{
public const string PK_Define = "PK";
public const string SK_Define = "SK";
[JsonProperty(PK_Define)]
public string PK { get; private set; } = string.Empty;
[JsonProperty(SK_Define)]
public string SK { get; private set; } = string.Empty;
public PrimaryKey()
{ }
public PrimaryKey(string partitionKey)
{
PK = partitionKey;
}
public PrimaryKey(string partitionKey, string sortKey)
{
PK = partitionKey;
SK = sortKey;
}
public bool isFilledPK()
{
if (true == PK.isNullOrWhiteSpace())
{
return false;
}
return true;
}
public bool isFilledSK()
{
if (true == SK.isNullOrWhiteSpace())
{
return false;
}
return true;
}
public void fillUpPrimaryKey(string partitionKey, string sortKey)
{
PK = partitionKey;
SK = sortKey;
}
public void fillUpPK(string partitionKey)
{
PK = partitionKey;
}
public void fillUpSK(string sortKey)
{
SK = sortKey;
}
public void setEmptySK()
{
SK = DynamoDbClient.SK_EMPTY;
}
public string toPKSK()
{
return $"{PK_Define}:{PK}-{SK_Define}:{SK})";
}
//=====================================================================================
// LINK_PKSK : "PK-SK"
//=====================================================================================
public static (Result, PrimaryKey?) parseLinkPKSK(LINK_PKSK linkPKSK)
{
var result = new Result();
var err_msg = string.Empty;
var pksk_parts = linkPKSK.Split('-');
if (pksk_parts.Length != 2)
{
err_msg = $"Invalid length of LinkPKSK !!! : 2 == length:{pksk_parts.Length}";
result.setFail(ServerErrorCode.DynamoDbDocLinkPkSkInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, null);
}
return (result, new PrimaryKey(pksk_parts[0], pksk_parts[1]));
}
public string toBasicString()
{
return $"PrimaryKey({PK_Define}:{PK}, {SK_Define}:{SK})";
}
}
//=============================================================================================
// DynamoDB의 Document Model에서 쿼리할때 옵션을 설정하는 클래스 이다.
//
// - kangms
//=============================================================================================
public static class DynamoDbQueryOperationOptionConfig
{
private static GetItemOperationConfig m_get_item_config = new();
private static TransactWriteItemOperationConfig m_transact_write_config_for_exist_primary_key = new();
private static TransactWriteItemOperationConfig m_transact_write_config_for_not_exist_primary_key = new ();
// Select 쿼리후 반환되는 필드 선택 설정
private static TransactGetItemOperationConfig m_transact_get_config = new();
public static void configure()
{
{
m_get_item_config.ConsistentRead = true;
}
{
var expression = new Amazon.DynamoDBv2.DocumentModel.Expression();
expression.ExpressionStatement = $"attribute_exists(#{PrimaryKey.PK_Define}) AND attribute_exists(#{PrimaryKey.SK_Define})";
expression.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define;
expression.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define;
m_transact_write_config_for_exist_primary_key.ConditionalExpression = expression;
m_transact_write_config_for_exist_primary_key.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.DocumentModel.ReturnValuesOnConditionCheckFailure.AllOldAttributes;
}
{
var expression = new Amazon.DynamoDBv2.DocumentModel.Expression();
expression.ExpressionStatement = $"attribute_not_exists(#{PrimaryKey.PK_Define}) AND attribute_not_exists(#{PrimaryKey.SK_Define})";
expression.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define;
expression.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define;
m_transact_write_config_for_not_exist_primary_key.ConditionalExpression = expression;
m_transact_write_config_for_not_exist_primary_key.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.DocumentModel.ReturnValuesOnConditionCheckFailure.AllOldAttributes;
}
}
public static TransactWriteItemOperationConfig getTransactWriteConfigForExistKey() => m_transact_write_config_for_exist_primary_key;
public static TransactWriteItemOperationConfig getTransactWriteConfigForNotExistKey() => m_transact_write_config_for_not_exist_primary_key;
public static GetItemOperationConfig getItemConfig() => m_get_item_config;
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public partial class DynamoDbClient
{
public class ConfigParam : IConfigParam
{
public string ServiceType { get; set; } = string.Empty;
public bool IsLocalDynamoDB { get; set; } = false;
public string UrlOfDynamoDb { get; set; } = string.Empty;
public string AccessKeyOfAws { get; set; } = string.Empty;
public string SecretKeyOfAws { get; set; } = string.Empty;
public string RegionOfAws { get; set; } = string.Empty;
public Result tryReadFromJsonOrDefault(JObject jObject)
{
var result = new Result();
var err_msg = string.Empty;
try
{
ServiceType = jObject["ServiceType"]?.Value<string>() ?? ServiceType;
UrlOfDynamoDb = jObject["Dynamodb"]?.Value<string>() ?? UrlOfDynamoDb;
var aws = jObject["AWS"];
NullReferenceCheckHelper.throwIfNull(aws, () => $"aws is null !!!");
IsLocalDynamoDB = aws["LocalDynamoDB"]?.Value<bool>() ?? IsLocalDynamoDB;
AccessKeyOfAws = aws["AccessKey"]?.Value<string>() ?? AccessKeyOfAws;
SecretKeyOfAws = aws["SecretKey"]?.Value<string>() ?? SecretKeyOfAws;
RegionOfAws = aws["Region"]?.Value<string>() ?? RegionOfAws;
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in tryReadFromJsonOrDefault() !!! : errorCode:{error_code}, exception:{e} - {this.getTypeName()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public string toBasicString()
{
return $"ConfigParam: ServiceType:{ServiceType}, UrlOfDynamoDb:{UrlOfDynamoDb}, isLocalDynamoDb:{IsLocalDynamoDB}, RegionOfAWS:{RegionOfAws}";
}
};
}

View File

@@ -0,0 +1,766 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Globalization;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Microsoft.AspNetCore.Server.IIS.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Google.Protobuf.WellKnownTypes;
using ServerCore; using ServerBase;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
using DYNAMO_DB_TABLE_NAME = System.String;
namespace ServerBase;
//=============================================================================================
// DynamoDb Document 기반 시간 설정
//
// - kangms
//=============================================================================================
public class DbTimestamp
{
public bool IsSet { get; set; } = false;
public DateTime ProcessedTime { get; set; } = DateTimeHelper.MinTime;
public void setProcessedTime(DateTime currentTime)
{
ProcessedTime = currentTime;
IsSet = true;
}
public void reset()
{
ProcessedTime = DateTimeHelper.MinTime;
IsSet = false;
}
}
//=============================================================================================
// DynamoDb Document 기반 Query Context
//
// - kangms
//=============================================================================================
public partial class DynamoDbDocumentQueryContext : IQueryContext
{
private readonly DYNAMO_DB_TABLE_FULL_NAME m_table_full_name;
private readonly Document m_document;
private QueryType m_query_type;
// TransactionCanceledException 관련 설정
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbDocumentQueryContext( DYNAMO_DB_TABLE_FULL_NAME tableFullName
, Document document, QueryType queryType )
{
m_table_full_name = tableFullName;
m_document = document;
m_query_type = queryType;
}
public DynamoDbDocumentQueryContext( DYNAMO_DB_TABLE_FULL_NAME tableFullName
, Document document, QueryType queryType
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler )
{
m_table_full_name = tableFullName;
m_document = document;
m_query_type = queryType;
m_exception_handler_nullable = exceptionHandler;
}
public bool isValid()
{
if (true == m_table_full_name.isNullOrWhiteSpace())
{
Log.getLogger().error($"TableFullName is Null or WhiteSpace !!! - {toBasicString()}");
return false;
}
if (null == m_document)
{
Log.getLogger().error($"DynamoDbDocument is null !!! - {toBasicString()}");
return false;
}
if (QueryType.None == m_query_type)
{
Log.getLogger().error($"DynamoDbDocument QueryType invalid !!! - {toBasicString()}");
return false;
}
return true;
}
public DYNAMO_DB_TABLE_FULL_NAME getTableFullName()
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == m_table_full_name.isNullOrWhiteSpace(), () => $"Invalid TableFullName !!!, Null of WhiteSpace !!! - {m_document.toPKSK()}");
return m_table_full_name;
}
public string toBasicString()
{
return $"{this.getTypeName()}, TableFullName:{m_table_full_name}, {m_query_type}, {m_document?.toBasicString()}";
}
}
//=============================================================================================
// PK(Partition Key) : Primary Key 내의 Partition Key + 해당 정보의 Domain 식별키
// SK(Sort Key) : Primary Key 내의 Sort Key, 해당 정보의 고유 식별키
// DocType : DynamoDbDocBase 상속 객체명
// Attrib ... : AttribBase 상속 객체명
// CreatedDateTime : Item Document 생성 시간
// UpdateedDateTime : Item Document 마지막 업데이트 시간 (생성, 삭제, 복구시 동일 시간으로 반영)
// DeletedDateTime : Item Document 삭제 시간
// RestoredDateTime : Item Document 복구 시간
// - kangms
//=============================================================================================
public abstract partial class DynamoDbDocBase : IRowData
{
// 관계된 Table Name, 특화된 테이블명이 있다면 재정의해야 한다. - kangms
public virtual DYNAMO_DB_TABLE_NAME TableName
{
get {
return DynamoDbDefine.TableNames.Main;
}
}
public const string CreatedDateTime = "CreatedDateTime";
public const string UpdatedDateTime = "UpdatedDateTime";
public const string DeletedDateTime = "DeletedDateTime";
public const string RestoredDateTime = "RestoredDateTime";
private PrimaryKey m_key = new();
// DocType은 DynamoDb Document 정보를 Read시 식별키로 사용되기 때문에 고유하게 정의되어야 한다 !!! - kangms
[JsonProperty("DocType")]
private readonly string m_doc_type;
public const string ATTRIBUTE_PATH = "attribute_path";
private ConcurrentDictionary<System.Type, IAttribWrapper> m_attrib_wrappers = new();
private DbTimestamp m_created_datetime = new();
private DbTimestamp m_updated_datetime = new();
private DbTimestamp m_deleted_datetime = new();
private DbTimestamp m_restored_datetime = new();
private string m_combination_key_for_pk = string.Empty;
private string m_combination_key_for_sk = string.Empty;
private Result m_initialize_result = new();
private bool m_is_enable_ttl = false;
private Int64 m_ttl_seconds = default;
private QueryType m_query_type = QueryType.None;
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbDocBase(string docType)
{
m_doc_type = docType;
}
// 필독 !!!
// 최초 등록시 TTL 설정을 적용해야 한다.
// TTL 설정이 적용되면 TTL 시간이 다시 적용됨을 유의 !!! - kangms
public DynamoDbDocBase(string docType, Int64 ttlSeconds)
{
m_doc_type = docType;
enableTTL(ttlSeconds);
}
public void SetTtlSeconds(Int64 ttlSeconds)
{
m_ttl_seconds = ttlSeconds;
}
public bool isNotSetupQueryType()
{
if(QueryType.None == m_query_type)
{
return true;
}
return false;
}
protected bool appendAttribWrapper(IAttribWrapper wrapper)
{
var result = getInitializeResult();
if (false == m_attrib_wrappers.TryAdd(wrapper.getAttribType(), wrapper))
{
var error_code = ServerErrorCode.DynamoDbDocAttribTypeDuplicated;
var err_msg = $"Failed to TryAdd AttribWrapper !!!, duplicated AttribType : AttribType:{wrapper.getAttribType()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return false;
}
return true;
}
public virtual async Task<Result> newDoc4Query()
{
var result = getInitializeResult();
if(result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
var current_time = DateTimeHelper.Current;
setCreatedDateTime(current_time);
changeUpdatedDateTime(current_time);
m_query_type = QueryType.Insert;
return result;
}
public virtual async Task<Result> updateDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
changeUpdatedDateTime(DateTimeHelper.Current);
m_query_type = QueryType.Update;
return result;
}
public virtual async Task<Result> upsertDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
var current_time = DateTimeHelper.Current;
setCreatedDateTime(current_time);
changeUpdatedDateTime(current_time);
m_query_type = QueryType.Upsert;
return result;
}
public virtual async Task<Result> deleteDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
setDeletedDateTime(DateTimeHelper.Current);
m_query_type = QueryType.Delete;
return result;
}
public virtual async Task<Result> onCopyFromDocument(Amazon.DynamoDBv2.DocumentModel.Document fromDocument)
{
return await Task.Run(() =>
{
var result = new Result();
var err_msg = string.Empty;
var doc_type = fromDocument.getDocType();
var pk = fromDocument.getPK();
var sk = fromDocument.getSK();
try
{
result = getInitializeResult();
if (result.isFail())
{
return result;
}
var error_code = fillUpPrimaryKey(pk, sk);
if (error_code.isFail())
{
err_msg = $"Failed to fillUpPrimaryKey() !!! : {error_code}";
result.setFail(error_code, err_msg);
return result;
}
var ttl_name = DynamoDbClient.TTL_NAME;
if (fromDocument.Contains(ttl_name))
{
fromDocument[ttl_name].AsString();
}
if (false == fillUpDocType(doc_type))
{
err_msg = $"Failed to fillUpDocType() !!! : docType:{doc_type} - PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocTypeNotMatch, err_msg);
return result;
}
foreach (var each in m_attrib_wrappers)
{
var attrib_wrapper = each.Value;
NullReferenceCheckHelper.throwIfNull(attrib_wrapper, () => $"attrib_wrapper is null !!! - docType:{doc_type}, PK:{pk}, SK:{sk}");
if (false == attrib_wrapper.onCopyFromDynamoDbEntry(fromDocument))
{
err_msg = $"Failed to onCopyFromDynamoDbEntry() !!! : attribType:{attrib_wrapper.getAttribType()} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocAttribWrapperCopyFailed, err_msg);
return result;
}
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.CreatedDateTime, out var created_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, CreatedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().error(err_msg);
}
else
{
setCreatedDateTime(created_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.UpdatedDateTime, out var updated_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, UpdatedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().error(err_msg);
}
else
{
setUpdatedDateTime(updated_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.DeletedDateTime, out var deleteded_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, DeletedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().debug(err_msg);
}
else
{
setDeletedDateTime(deleteded_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.RestoredDateTime, out var restored_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, RestoredDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().debug(err_msg);
}
else
{
setRestoredDateTime(restored_time);
}
}
catch (Exception e)
{
err_msg = $"Exception !!!, Failed to perform in DynamoDbDocBase.onCopyFromDocument() !!! : exception:{e} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocCopyFailedFromDocument, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
return result;
});
}
public virtual async Task<(Result, Amazon.DynamoDBv2.DocumentModel.Document)> onCopyToDocument()
{
return await Task.Run(() =>
{
var toDocument = new Amazon.DynamoDBv2.DocumentModel.Document();
var result = new Result();
var err_msg = string.Empty;
var doc_type = getDocType();
var pk = onMakePK();
var sk = onMakeSK();
try
{
result = getInitializeResult();
if (result.isFail())
{
return (result, toDocument);
}
var error_code = fillUpPrimaryKey(onMakePK(), onMakeSK());
if (error_code.isFail())
{
err_msg = $"Failed to fillUpPrimaryKey() !!! : {error_code}";
result.setFail(error_code, err_msg);
return (result, toDocument);
}
if (false == fillUpDocType(getDocType()))
{
return (result, toDocument);
}
toDocument[PrimaryKey.PK_Define] = getPK();
toDocument[PrimaryKey.SK_Define] = getSK();
if (isEnableTTL())
{
toDocument[DynamoDbClient.TTL_NAME] = getTTLSeconds();
}
toDocument["DocType"] = getDocType();
foreach (var each in m_attrib_wrappers)
{
each.Value.onCopyToDynamoDbEntry(ref toDocument);
}
if(true == getCreatedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.CreatedDateTime] = getCreatedDateTime().ProcessedTime;
}
if (true == getUpdatedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.UpdatedDateTime] = getUpdatedDateTime().ProcessedTime;
}
if (true == getDeletedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.DeletedDateTime] = getDeletedDateTime().ProcessedTime;
}
if (true == getRestoredDateTime().IsSet)
{
toDocument[DynamoDbDocBase.RestoredDateTime] = getRestoredDateTime().ProcessedTime;
}
return (result, toDocument);
}
catch(Exception e)
{
err_msg = $"Exception !!!, Failed to perform in DynamoDbDocBase.onCopyToDocument() !!! : exception:{e} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocCopyFailedToDocument, err_msg);
Log.getLogger().fatal(err_msg);
return (result, toDocument);
}
});
}
public void copyTimestampsFromOriginDocBase(DynamoDbDocBase originDocBase)
{
setCreatedDateTime(originDocBase.getCreatedDateTime().ProcessedTime);
setUpdatedDateTime(originDocBase.getUpdatedDateTime().ProcessedTime);
setDeletedDateTime(originDocBase.getDeletedDateTime().ProcessedTime);
setRestoredDateTime(originDocBase.getRestoredDateTime().ProcessedTime);
}
protected abstract string onGetPrefixOfPK();
protected virtual string onMakePK()
{
return $"{onGetPrefixOfPK()}{getCombinationKeyForPK()}";
}
protected virtual string onGetPrefixOfSK() { return ""; }
protected virtual string onMakeSK() { return getCombinationKeyForSK(); }
protected ServerErrorCode fillUpPrimaryKey(string partitionKey, string sortKey)
{
var err_msg = string.Empty;
var error_code = onCheckAndSetPK(partitionKey);
if (error_code.isFail())
{
return error_code;
}
if(0 < sortKey.Length)
{
if(true == sortKey.isEmptySK())
{
getPrimaryKey().setEmptySK();
}
else
{
error_code = onCheckAndSetSK(sortKey);
if (error_code.isFail())
{
err_msg = $"Failed to onCheckAndSetSK() !!!, param sortKey exist : sorkKey:{sortKey}, {error_code.toBasicString()} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return error_code;
}
}
}
return ServerErrorCode.Success;
}
protected virtual ServerErrorCode onCheckAndSetPK(string partitionKey)
{
var error_code = onFillupCombinationKeyForPK(partitionKey, out var combination_key_for_pk);
if (error_code.isFail())
{
return error_code;
}
getPrimaryKey().fillUpPK(partitionKey);
setCombinationKeyForPK(combination_key_for_pk);
return ServerErrorCode.Success;
}
public virtual ServerErrorCode onFillupCombinationKeyForPK(string partitionKey, out string fillupKey)
{
fillupKey = string.Empty;
var prefix_of_pk = onGetPrefixOfPK();
if (true == prefix_of_pk.isNullOrWhiteSpace())
{
fillupKey = partitionKey;
}
else
{
var combination_key_for_pk = partitionKey.Replace(onGetPrefixOfPK(), "");
if (0 >= combination_key_for_pk.Length)
{
var err_msg = $"PK combination 0 is length !!! : PrimaryKey:{partitionKey} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return ServerErrorCode.DynamoDbDocPkInvalid;
}
fillupKey = combination_key_for_pk;
}
return ServerErrorCode.Success;
}
protected virtual ServerErrorCode onCheckAndSetSK(string sortKey)
{
return ServerErrorCode.FunctionNotImplemented;
}
public virtual ServerErrorCode onFillupCombinationKeyForSK(string sortKey, out string fillupKey)
{
fillupKey = string.Empty;
var prefix_of_sk = onGetPrefixOfSK();
if(true == prefix_of_sk.isNullOrWhiteSpace())
{
fillupKey = sortKey;
}
else
{
var combination_key_for_sk = sortKey.Replace(prefix_of_sk, "");
if (0 >= combination_key_for_sk.Length)
{
var err_msg = $"SK combination 0 is length !!! : SortKey:{sortKey} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return ServerErrorCode.DynamoDbDocSkInvalid;
}
fillupKey = combination_key_for_sk;
}
return ServerErrorCode.Success;
}
public virtual ServerErrorCode onApplyPKSK()
{
return fillUpPrimaryKey(onMakePK(), onMakeSK());
}
protected Int64 getTTLSeconds() => m_ttl_seconds;
protected virtual Task<Result> onPrepareCRUD()
{
return Task.FromResult(new Result());
}
private bool fillUpDocType(string docType)
{
var err_msg = string.Empty;
if ( false == m_doc_type.Equals(docType, StringComparison.OrdinalIgnoreCase) )
{
err_msg = $"DocType Not matched !!! : currDocType:{m_doc_type} == reqDocType:{docType} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
return true;
}
public TAttrib? getAttrib<TAttrib>()
where TAttrib : AttribBase
{
var err_msg = string.Empty;
var to_find_attrib_type = typeof(TAttrib);
if (false == m_attrib_wrappers.TryGetValue(to_find_attrib_type, out var lookup_attrib))
{
err_msg = $"Not found AttribType !!! : toFindAttribType:{to_find_attrib_type.Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return null;
}
var found_attrib = lookup_attrib.getAttribBase() as TAttrib;
if (null == found_attrib)
{
err_msg = $"Failed to cast AttribType : toCastAttribType:{to_find_attrib_type.Name} == lookupAttribType:{lookup_attrib.getAttribType().Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return null;
}
return found_attrib;
}
protected void setCreatedDateTime(DateTime currentTime)
{
m_created_datetime.setProcessedTime(currentTime);
}
protected void setUpdatedDateTime(DateTime currentTime)
{
m_updated_datetime.setProcessedTime(currentTime);
}
protected void changeUpdatedDateTime(DateTime currentTime)
{
m_updated_datetime.setProcessedTime(currentTime);
}
protected void setDeletedDateTime(DateTime currentTime)
{
m_deleted_datetime.setProcessedTime(currentTime);
}
protected void setRestoredDateTime(DateTime currentTime)
{
m_restored_datetime.setProcessedTime(currentTime);
}
public virtual string toAttribAllString()
{
var attrib_string = string.Empty;
foreach(var attrib_wrapper in m_attrib_wrappers.Values)
{
attrib_string += attrib_wrapper.getAttribBase().toBasicString();
attrib_string += ", ";
}
var found_index = attrib_string.LastIndexOf(", ");
if(found_index != -1)
{
attrib_string.Remove(found_index);
}
return attrib_string;
}
protected bool isEnableTTL() => m_is_enable_ttl;
public void desableTTL()
{
m_ttl_seconds = 0;
m_is_enable_ttl = false;
}
public void enableTTL(long ttlSeconds)
{
m_ttl_seconds = ttlSeconds;
m_is_enable_ttl = true;
}
public string toDocTypePKSK()
{
return $"{this.getTypeName()}:{getPrimaryKey().toBasicString()}";
}
public string toJsonStringOfAttribs()
{
var json_object = new JObject();
foreach (var each in m_attrib_wrappers)
{
if(false == each.Value.onFillupJsonStringToJObject(ref json_object))
{
var err_msg = $"Failed to onFillupJsonStringToDynamoDbEntry() !!! : {each.Value.getAttribType().Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
}
}
return JsonConvert.SerializeObject(json_object, Formatting.Indented);
}
public virtual string toBasicString()
{
return $"{this.getTypeName()}: tableName:{TableName}, {getPrimaryKey().toBasicString()}"
+ $", {getCreatedDateTime().ProcessedTime.toString("o")}"
+ $", {getUpdatedDateTime().ProcessedTime.toString("o")}"
+ $", {getDeletedDateTime().ProcessedTime.toString("o")}"
+ $", {getRestoredDateTime().ProcessedTime.toString("o")}";
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// DynamoDb ItemRequest 기반 Query Context
//
// - kangms
//=============================================================================================
public partial class DynamoDbItemRequestQueryContext : IQueryContext
{
private readonly AmazonDynamoDBRequest m_db_request;
private QueryType m_query_type;
// TransactionCanceledException 관련 설정
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbItemRequestQueryContext( AmazonDynamoDBRequest dbRequest, QueryType queryType )
{
m_db_request = dbRequest;
m_query_type = queryType;
}
public DynamoDbItemRequestQueryContext( AmazonDynamoDBRequest dbRequest, QueryType queryType
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler )
{
m_db_request = dbRequest;
m_query_type = queryType;
m_exception_handler_nullable = exceptionHandler;
}
public bool isValid()
{
if (null == m_db_request)
{
Log.getLogger().error($"DynamoDbDocument is null !!! - {toBasicString()}");
return false;
}
if (QueryType.None == m_query_type)
{
Log.getLogger().error($"DynamoDbDocument QueryType invalid !!! - {toBasicString()}");
return false;
}
return true;
}
public string toBasicString()
{
return $"{this.getTypeName()}, {m_query_type}, {m_db_request?.toBasicString()}";
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public class DynamoDbQueryExceptionNotifier
{
public const string Success = "None"; // 쿼리 성공
public const string ConditionalCheckFailed = "ConditionalCheckFailed"; // 쿼리 실패 : 조건들이 충족하지 못해 실패 했다.
public const string ItemCollectionSizeLimitExceeded = "ItemCollectionSizeLimitExceeded"; // 쿼리 실패 : 아이템 제한 개수 초과로 실패 했다.
public class ExceptionHandler
{
public ExceptionHandler(string reasonCode, ServerErrorCode returnToErrorCode)
{
ReasonCode = reasonCode;
ReturnToErrorCode = returnToErrorCode;
}
public bool hasReasonCode(string reasonCode)
{
return ReasonCode.Equals(reasonCode, StringComparison.OrdinalIgnoreCase);
}
public string ReasonCode { get; set; }
public ServerErrorCode ReturnToErrorCode { get; set; }
}
private Int32 m_query_seq_no = 0;
private readonly Dictionary<Int32, ExceptionHandler> m_exception_handlers = new();
public DynamoDbQueryExceptionNotifier()
{
}
public bool registerExceptionHandler(ExceptionHandler? exceptionHandler)
{
m_query_seq_no++;
if(null == exceptionHandler)
{
return false;
}
if(true == m_exception_handlers.ContainsKey(m_query_seq_no))
{
return false;
}
m_exception_handlers[m_query_seq_no] = exceptionHandler;
return true;
}
public ExceptionHandler? findExceptionHandler(Int32 querySeqNo, string reasonCode)
{
if(false == m_exception_handlers.TryGetValue(querySeqNo, out var exception_handler))
{
return null;
}
if(false == exception_handler.hasReasonCode(reasonCode))
{
return null;
}
return exception_handler;
}
}