[Unity] AI 시대의 클린 아키텍처
![[Unity] AI 시대의 클린 아키텍처](/_next/image?url=https%3A%2F%2Fdata.develog.develrocket.com%2Fupload%2Fdevelog%2Fuser_1776858831954%2F1786580564135-4xi51k%2F2b007343-f49b-49a6-abce-6f9cefb1b318.png&w=3840&q=75)
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요.
로버트 C 마틴의 <클린 아키텍처>는 엉클밥의 블로그 포스트에서 시작되었다. 오늘날까지도 많은 개발자에게 사랑받는 소프트웨어 설계 아이디어이며, 동시에 그만큼 많이 비판받는 것이기도 하다.
참고로 나는 전자다(아직 잘 안 써버릇해서 그런 듯함)
이 글은 UnityKorea 유튜브 채널에 올라온 유나이트 서울 2026 발표 중 하나를 본 것이 작성의 계기가 되었다.
소스코드의 의존성은 항상 안쪽을 향해야한다. 정책(골드가 부족하면 구매 실패)은 세부사항(PlayerPrefs에 저장)에 끌려다니면 안 된다.
여기서 '안쪽'이 무엇인지에 대해서는 아래 그림을 참고하면 좋다.

클린 아키텍처는 핵심 아이디어를 구현하기 위한 계층 구조를 갖는다. 가장 안쪽의 Enterprise Business Rules(비즈니스 로직)은 그 자체로 완결되고, 바깥쪽으로 갈수록 안쪽에 의존한다.
도메인 층은 비즈니스 규칙이 자리한다. 여기있는 것들은 어느 게임의 규칙에 해당한다. 서드파티에 의존하지 않고 외부 툴에 의존하지 않는 순수 C#(POCO) 코드로 이루어진다.
보통 도메인이라고 부르는데, 아래와 같은 것들이 이 계층에 들어간다.
애플리케이션 계층은 클린아키텍처에서 가장 중요하고 어려운 부분이라고 생각한다. 개념적으로는 소프트웨어 내에서 하나의 시나리오 규칙을 정의한다.
DoSomething 과 같이 동사와 목적어로 명명되어서, 단일 동작에 대해 구현한다. 입력 Port를 구현한다.기억해둘 것은, 여기에 외부 계층이 구현할 포트(인터페이스)가 있다는 점이다. 여기가 DIP - '인터페이스가 안쪽, 구현체가 바깥쪽'이 적용된 부분이며 로직과 View 층의 분리가 가능해지는 핵심이다.
어댑터 계층은 개념적으로는 안쪽과 소통하기 위한 어댑터 역할을 하는데, 구체적으로는 아래와 같은 것들이 있다.
중요한건 이들이 전부 어댑터라는 사실이다. 이들은 모두 애플리케이션층이 선언한 포트 인터페이스를 구현한다. 이것이 매우 중요하다.
Unity 게임 개발자라면, 여기부터 MonoBehaviour라는 단어가 나오기 시작한다. 이전 계층까지는 using UnityEngine; 을 쓸 수 없게 asmdef로 강제(noEngineReferences: true)하기 때문이다. 이 계층에 와서야 외부 API와의 소통이 이루어진다.
이 계층의 특징은 가장 코드가 적다는 점일 것이다. 예시는 아래와 같다.
사실 Framework랑 Adapter의 경계는 다른 계층에 비해 좀 흐릿하다. 둘 다 using UnityEngine이 가능하고 엔진 등 서드파티에 종속될 수 있지만, 그럼에도 분류하는 것은 단순한 유지보수에서의 편의성 때문이다.
예를 들어 엔진이 깨졌다면(Unity6에서 Unity7로의 버전업 등) 어댑터 구현체 API 몇 개를 고치면 되고(UniTask -> Awaitable 등), Framework의 Monobehaviour나 프리팹 등의 에셋은 박살이 날 것이다.
실제로 이 경계를 사이에 둔 어댑터와 프레임워크는 asmdef를 같이 쓰기도 한다.
단점부터 짚자.
그래서 인게임 전투처럼 매 프레임 계산이 빡빡하게 이루어지는 곳에는 부적합하고, 메타게임에 쓰는 편이 좋다.
이제 장점 짚자.
사실 이 글을 쓰게 된 계기도 AI때문이다. 아무리 구조를 잘 짜놓으려 해도 AI가 뱉어내는 코드를 읽는 데는 한 세월이다. 팀프로젝트에선 읽어야할 코드가 팀 인원만큼의 에이전트로부터 나온다. 그걸 읽다 보면 정신이 나간다.
중요한건 코드가 제대로 작동하는지에 대한 것밖에 없다. 그래서 단위 테스트를 도입했으나 잘 지켜지지 않는다. 고민을 해보니까, 애초에 프로젝트 내에 하네스가 느슨했던 것 뿐이었다. 모듈을 제대로 분리해서 계층별로 작업 디렉토리를 관리했으면 조금 나았을까? 그리고 로직을 유즈케이스나 도메인에 몰아넣고 테스트를 일일이 작성하게 에이전트에 강제했더라면 어땠을까?
클린 아키텍처의 장점은 이 고민을 어느 정도 해소해준다는 점이다. 이 설계는 내가 읽어야할 코드를 테스트 코드와 테스트 결과만으로 줄어든다!
참고로 계층 분리는 밥 아저씨가 제안한 것이고, 실제로 중요한 것은 의존성이 안쪽이라는 사실 뿐이다. 이것만 지키면 계층이 몇 개든 상관이 없다.
그래도 항상 하는 생각이 완벽한 건 없다는 생각이다. 언제나 이런 생각이 들 때 프로젝트를 엎는 것은 최악의 선택이었다. 앞으로는 이 의존성의 방향을 잘 고민하면서 코드를 짜야겠다 싶었다.
좌우지간.
나는 불어나는 규모에 대응할 수 있는 안정적인 아키텍처나 패턴에 관심이 조금 있는 편인데, 클린아키텍처는 옛날에 로버트C마틴씨의 책을 갖고 있었으나 어려워서 이해가 안 돼 읽다 말았다. 유니티 유나이트 서울 2026에서 이걸 다뤄줘서 도움이 많이 됐고, 최근에 알게 된 Unity Test Framework, *.asmdef 의 올바른 사용법에 대해 알게 된 듯하다.
그리고 먼저 공부해야 했던 것은 SOLID가 아니라 클린 아키텍처의 핵심 개념이 아니었을까 싶기도 하고. DIP같은 건 알아봤자 왜 쓰는지 몰라서는 의미가 없다. 모듈이 뭔지도 모르는데 DIP를 공부해서 무슨 의미가 있을까. 그리고 지금까진 그랬던 것 같다. 역시 나무가 아닌 숲을 봐야 된다는 느낌;
끗.
이미지: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
SerializeReference
![[Unity] 튜토리얼 설계](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1787322579554-zopplu/_____2026-08-21_232731.png)
튜토리얼 설계
![[Unity] Event Trigger Component](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1786976296699-a3tu75/_____2026-08-17_231353.png)
Unity Event Trigger 컴포넌트
![[Unity] 튜토리얼 설계](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1787322579554-zopplu/_____2026-08-21_232731.png)
![[Unity] Event Trigger Component](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1786976296699-a3tu75/_____2026-08-17_231353.png)