업무 모듈 개발
업무 모듈은 화면(wwwroot/{모듈}/view/…), 계약(Contracts/{종류}/…), 설정(module.json)을 한 프로젝트로 묶은 .NET 라이브러리입니다. 호스트(ack 또는 rdy)가 module.json과 DLL을 읽고 IModuleInitializer를 실행합니다. 기본 모듈(dbclient, transact 등)도 같은 구조입니다. 모듈 설정은 module.json, 메뉴는 menus.json을 보세요.
폴더 구성
{Module}/
├─ {Module}.csproj # Library, 호스트가 로드합니다
├─ module.json # 모듈 설정 (비밀 포함)
├─ ModuleInitializer.cs # 서비스·미들웨어·정적 파일 등록
├─ Areas/{Module}/Controllers # 모듈 API (Index, Managed …)
├─ Entity/ Extensions/ Services/ Settings/
├─ Contracts/
│ ├─ transact/{App}/{Project}/{ID}.txn
│ ├─ dbclient/{App}/{Project}/{ID}.dbc
│ ├─ function|command|graphclient|prompter/…
│ └─ repository/{App}/{Module}.rpo
└─ wwwroot/{Module}/
├─ main.html, main.js, main.css # 업무 셸 (탭, 메뉴, 버튼)
├─ menus.json # 메뉴
├─ syn.config.json # 화면 런타임 설정
└─ view/{App}/{Project}/{ID}.html|js
모듈마다 일부가 다릅니다. 화면 경로에 {App} 폴더가 없거나 sample/ 아래에 화면을 두는 예제도 있으므로 새 파일은 같은 모듈의 기존 배치를 따르세요. 계약 확장자는 계약 확장자를 보세요.
식별자
| 이름 | 의미 | 쓰이는 곳 |
|---|---|---|
ModuleID | 모듈 폴더·어셈블리 이름 | module.json, 웹 경로 /{ModuleID}/…, wwwroot/{ModuleID}/ |
ApplicationID | 최상위 애플리케이션(예 HDS) | 계약 폴더 1단계, 계약 header, syn.config.json |
ProjectID | 업무 묶음 코드 | 계약 폴더 2단계, 화면 폴더 |
TransactionID | 화면·거래 ID | 파일 이름 |
DataSourceID, RepositoryID | 서버 설정의 DB 원본, 파일 저장소 | 계약 header, 화면 컨트롤 |
모듈 폴더 이름과 ProjectID는 다른 값입니다. 서로 바꿔 쓰지 마세요(식별자와 파일 경로).
로딩
ModuleID, 어셈블리 이름, 폴더 이름,module.json의ModuleID가 같아야 합니다. 호스트는AppSettings:LoadModules(또는--modules)의 이름과modules/{이름}/module.json을 맞춰{이름}.dll을 로드합니다.ack의 DLL 로딩 규칙(선택한 모듈의 진입 DLL만 기본 로드 컨텍스트에 로드하고 의존 DLL은 요청 시 해석)은 모듈 DLL 로딩을 보세요. 리플렉션으로 다른 모듈의 타입을 찾는 코드는 먼저Assembly.Load("이름")을 호출합니다.rdy는command,dbclient,function,graphclient,logger,prompter,repository,transact,wwwroot9개를 정적 결합하고 나머지는 동적으로 로드합니다(운영 호스트).- 호스트는
IModuleInitializer구현을 찾아ConfigureServices(DI 등록)와Configure(미들웨어, 정적 파일)를 호출합니다. MVC 컨트롤러는 ApplicationPart로 합성됩니다.
ModuleInitializer
public class ModuleInitializer : IModuleInitializer
{
public string? ModuleID;
public ModuleInitializer() { ModuleID = typeof(ModuleInitializer).Assembly.GetName().Name; }
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment environment, IConfiguration configuration)
{
var module = GlobalConfiguration.Modules.FirstOrDefault(p => p.ModuleID == ModuleID);
if (module == null) { return; }
var configurationText = PlatformSettings.ReadAllText(module.ModuleSettingFilePath); // module.{os}.json 병합 포함
var moduleConfigJson = JsonConvert.DeserializeObject<ModuleConfigJson>(configurationText);
// ModuleConfiguration의 static 필드에 값을 복사하고 경로는 GlobalConfiguration.GetBaseDirectoryPath(값, module.BasePath)로 해석
services.AddHttpClient<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment? environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider)
{
// wwwroot/{ModuleID}를 RequestPath "/{ModuleID}"로 UseDefaultFiles + UseStaticFiles
// OnPrepareResponse에서 WithOnlyIPFilter 검사와 CORS 정책 적용
}
}
- 설정 파일 누락이나 역직렬화 실패는
FileNotFoundException·FileLoadException으로 시작을 중단하는 것이 기존 모듈의 방식입니다. 조용히 기본값으로 넘기지 마세요. AuthorizationKey가 비어 있을 때SystemID + RunningEnvironment + HostName같은 파생 값을 쓰는 모듈이 많습니다. 운영에서는 명시 값을 환경 변수로 주입하세요.- 로그는 Serilog 구조화 로그로 카테고리를 첫 인자로 씁니다:
Log.Logger.Error("[{LogCategory}] 메시지", "{ModuleID} ModuleInitializer/ConfigureServices").
설정 엔티티
Entity/ModuleConfigJson.cs:module.json의 스키마입니다. 새 옵션은 여기에 속성을 추가하고 기본값을 생성자에서 지정합니다.Entity/ModuleConfiguration.cs: 런타임이 읽는 static 필드입니다.ModuleConfig와 이름·형식이 같은 필드는 호스트의IsConfigurationWatching이 실시간으로 채우고, 대응 필드가 없거나 변환이 필요한 항목은 "재시작 필요"로 기록됩니다.- 새 옵션을 추가할 때는
module.json(기본값),ModuleConfigJson,ModuleConfiguration, 설정 화면(module-settings.*), 사용하는 코드, 템플릿의module.json을 함께 고칩니다.
컨트롤러와 접근 제어
[Area("mymodule")]
[Route("[area]/api/[controller]")]
[ApiController]
[EnableCors]
[TypeFilter(typeof(RequestOriginActionFilter))] // 관리 API: 요청 출처 / API 키 검사
public partial class ManagedController(...) : BaseController
{
[HttpGet("[action]")]
public ActionResult ResetContract()
{
if (HttpContext.IsAllowAuthorization() == false) { return BadRequest(); }
...
}
}
- 모듈 관리 API는
RequestOriginActionFilter(AppSettings:Security:RequestOriginApiAccess)와 모듈의IsAllowAuthorization()을 모두 통과해야 합니다. 판정 규칙(AuthorizationKey일치 또는AllowClientIP, 실패 시400)은 계약 관리 API 공통 규칙을 보세요. RequestOriginActionFilter를 붙이는 위치는 모듈마다 다릅니다. 대부분 컨트롤러 클래스에 붙이지만checkup의ManagedController는 메서드에 붙입니다. 새 컨트롤러는 같은 모듈의 기존 패턴을 따르세요.- 업무 API(거래 실행 등)의 인증과 권한은 transact 계약(
Authorize,Roles,AccessScreenID)에 둡니다. 인증 없이 데이터를 노출하는 컨트롤러를 만들지 마세요. - 장시간 작업에서
lock을 오래 잡지 말고, 예외는 로그에 남긴 뒤 일반 메시지로 응답하세요(스택·경로를 노출하지 않습니다).
모듈 간 통신
- 같은 프로세스의 모듈 사이는 Mediator
IRequest<object?>/IRequestHandler이벤트로 통신합니다. 이벤트 클래스를{모듈}.Events.{이름}으로 노출하고, 호출 측은Assembly.Load(모듈).GetType("…")로 인스턴스를 만들어mediator.Send합니다. 발행·구독 이름은module.json의EventAction,SubscribeAction에 등록하며 실행 중에도 호스트의 MediatR 설정에 반영될 수 있습니다(/moduleconfiguration/mediatr). 개념은 In-process / Out-process 메시징을 보세요. - 모듈이 거래를 실행하려면
BusinessServerUrl(transact 실행 URL)로TransactionClient.TransactionDirect나ModuleApiClient를 씁니다. 내부 호출도 요청 출처 검사와 인증 정책을 통과해야 합니다. - 로그는
logger모듈로 보내는LogServerUrl/IsLogServer설정을 따르며 실패하면 로컬 로그로 대체합니다.
런타임 설정 반영
IModuleRuntimeConfiguration.ReloadModuleConfiguration(ModuleInfo, string)을 구현하면 module.json 변경을 모듈이 직접 처리합니다. 결과는 ModuleConfigurationReloadResult의 AppliedKeys, RestartRequiredKeys, Errors로 돌려줍니다. IModuleRuntimeConfigurationPropertyHandler(CanHandleModuleConfigurationProperty, ApplyModuleConfigurationProperty)는 특정 속성만 직접 처리할 때 씁니다. 데이터 원본 같은 캐시형 설정은 새 목록 전체를 검증한 뒤 한 번에 교체하고 실 패하면 이전 설정을 유지하는 패턴을 따르세요.
빌드와 배포 부작용
모듈 csproj는 보통 OutputType=Library이고 wwwroot/**, Contracts/**, module.json을 출력으로 복사하며 Contracts/function/**를 Compile에서 제외합니다. PostBuild가 환경 변수가 가리키는 런타임 폴더를 삭제한 뒤 다시 복사하는 프로젝트가 흔하므로 빌드 한 번이 실행 중인 환경을 바꿀 수 있습니다. 원본 저장소를 빌드하기 전에 csproj의 PreBuild·PostBuild와 해당 환경 변수의 실제 값을 확인하세요. 빌드 성공은 계약 복사, 캐시 갱신, DB 접속 성공을 증명하지 않습니다(프로젝트마다 다르므로 확인 필요).
체크리스트
ModuleID= 폴더 = 어셈블리 =module.json이름이고LoadModules에 등록했습니다(rdy로 정적 결합하려면 프로젝트 참조도).- 설정 누락 시 시작을 중단하고, 비밀은 환경 변수로 주입하며, 기본
AllowClientIP와 키를 운영에서 좁혔습니다. - 컨트롤러: Area·Route 규칙을 따르고 관리 API에 출처 필터와
IsAllowAuthorization을 적용했습니다. - 새 옵션이
module.json, 엔티티,ModuleConfiguration, 설정 화면, 템플릿에 모두 반영됐습니다. - 검증:
dotnet build <csproj>→ 필요한 모듈만으로ack --modules=…기동 →/checkip→ 대상 API·거래 → 설정 변경이 실시간으로 반영되는지 재시작이 필요한지 확인.