Repository: Kernel360/KBE5_CKT_BE
Pull request #2: feat: 멀티 모듈 구성하기
Author: login: maximum-zero
Labels: name: ✨ feat
description: 새로운 기능 추가
Created: 2025-05-22T05:52:21Z | Merged: 2025-05-23T02:38:53Z

## #️⃣ 연관된 이슈
<!-- ex) #이슈번호[, #이슈번호] -->

> #1 


## 📝 작업 내용
<!-- 이번 PR에서 작업한 내용을 설명해 주세요. (이미지 첨부 가능) -->

> 멀티 모듈 프로젝트 구성하기
> 1. 루트 프로젝트 및 settings.gradle 및 build.gradle 공통 설정
> 2. ckt-core 모듈 생성 (핵심 비즈니스 도메인 (JPA 엔티티, 값 객체, 공통 DTO 등) 정의를 위한 순수 자바 모듈)
> 3. ckt-admin 모듈 생성 (관리자 기능을 담당하는 실행 가능한(Spring Boot) 모듈)
> 4. ckt-core 에서 CompanyEntity를 가져와서 ckt-admin 모듈에서 company 저장 api 생성


## 👀 리뷰어 가이드라인
<!-- 리뷰어가 중점적으로 확인해야 할 사항을 작성해 주세요. -->
<!-- ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? -->

> - 멀티 모듈 구성 확인 부탁드립니다.
> - ckt-core 와 ckt-domain 중 고민하다 타 프로젝트를 보니까 공통 에러 정의와 같은 내용도 들어가기에 core라는 명칭으로 생성해봤는데, 네이밍 어떠신지 의견 부탁드립니다.
> - 혹시 멀티 모듈에서 `application.yml` 설정도 공통으로 정의가 될까요?
>   - ex. ckt-admin 의 `application.yml` 의 jpa ddl 이 create 이고, ckt-admin2 의 `application.yml` 의 jpa ddl 이 update 으로 설정했다고 가정하고 두 모듈이 같은 db를 바라보면 문제가 생기지 않을까 싶습니다 (물론 운영에서는 해당 옵션은 none 을 사용하겠지만..)




Diff:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..47c6d217
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+max_line_length = 120
+
+[*.java]
+indent_style = space
+indent_size = 4
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..8af972cd
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..11638fa0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### .env ###
+.env
+.env.*
diff --git a/admin/Dockerfile b/admin/Dockerfile
new file mode 100644
index 00000000..4eae3fea
--- /dev/null
+++ b/admin/Dockerfile
@@ -0,0 +1,4 @@
+FROM amazoncorretto:17
+WORKDIR /app
+COPY build/libs/*.jar app.jar
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/admin/build.gradle b/admin/build.gradle
new file mode 100644
index 00000000..81841291
--- /dev/null
+++ b/admin/build.gradle
@@ -0,0 +1,20 @@
+dependencies {
+    implementation project(':core')
+
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+
+    // DB
+    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
+    runtimeOnly 'com.mysql:mysql-connector-j'
+
+    // JPA
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+}
+
+tasks.bootJar {
+    enabled = true
+}
+
+tasks.jar {
+    enabled = true
+}
diff --git a/admin/src/main/java/kernel360/ckt/admin/CktAdminApplication.java b/admin/src/main/java/kernel360/ckt/admin/CktAdminApplication.java
new file mode 100644
index 00000000..1cecdcf7
--- /dev/null
+++ b/admin/src/main/java/kernel360/ckt/admin/CktAdminApplication.java
@@ -0,0 +1,19 @@
+package kernel360.ckt.admin;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@SpringBootApplication
+@EntityScan(basePackages = "kernel360.ckt.core")
+@ComponentScan(basePackages = {"kernel360.ckt.core", "kernel360.ckt.admin"})
+@EnableJpaRepositories(basePackages = "kernel360.ckt.core")
+public class CktAdminApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(CktAdminApplication.class, args);
+    }
+
+}
diff --git a/admin/src/main/java/kernel360/ckt/admin/controller/CompanyController.java b/admin/src/main/java/kernel360/ckt/admin/controller/CompanyController.java
new file mode 100644
index 00000000..b6970e1c
--- /dev/null
+++ b/admin/src/main/java/kernel360/ckt/admin/controller/CompanyController.java
@@ -0,0 +1,28 @@
+package kernel360.ckt.admin.controller;
+
+import kernel360.ckt.admin.dto.request.CompanyCreateRequest;
+import kernel360.ckt.admin.dto.response.CompanyCreateResponse;
+import kernel360.ckt.admin.service.CompanyService;
+import kernel360.ckt.core.domain.entity.CompanyEntity;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequiredArgsConstructor
+@RequestMapping("/api/v1/company")
+@RestController
+public class CompanyController {
+    private final CompanyService companyService;
+
+    @PostMapping
+    ResponseEntity<CompanyCreateResponse> createCompany(@RequestBody CompanyCreateRequest request) {
+        CompanyEntity companyEntity = companyService.create(request);
+        return ResponseEntity.ok(
+            CompanyCreateResponse.from(companyEntity)
+        );
+    }
+
+}
diff --git a/admin/src/main/java/kernel360/ckt/admin/dto/request/CompanyCreateRequest.java b/admin/src/main/java/kernel360/ckt/admin/dto/request/CompanyCreateRequest.java
new file mode 100644
index 00000000..a2ef439c
--- /dev/null
+++ b/admin/src/main/java/kernel360/ckt/admin/dto/request/CompanyCreateRequest.java
@@ -0,0 +1,21 @@
+package kernel360.ckt.admin.dto.request;
+
+import kernel360.ckt.core.domain.entity.CompanyEntity;
+
+public record CompanyCreateRequest(
+    String email,
+    String password,
+    String name,
+    String ceoName,
+    String telNumber
+) {
+    public CompanyEntity toEntity() {
+        return CompanyEntity.builder()
+            .email(email)
+            .password(password)
+            .name(name)
+            .ceoName(ceoName)
+            .telNumber(telNumber)
+            .build();
+    }
+}
diff --git a/admin/src/main/java/kernel360/ckt/admin/dto/response/CompanyCreateResponse.java b/admin/src/main/java/kernel360/ckt/admin/dto/response/CompanyCreateResponse.java
new file mode 100644
index 00000000..23a54c6c
--- /dev/null
+++ b/admin/src/main/java/kernel360/ckt/admin/dto/response/CompanyCreateResponse.java
@@ -0,0 +1,19 @@
+package kernel360.ckt.admin.dto.response;
+
+import kernel360.ckt.core.domain.entity.CompanyEntity;
+
+public record CompanyCreateResponse(
+    String email,
+    String name,
+    String ceoName,
+    String telNumber
+) {
+    public static CompanyCreateResponse from(CompanyEntity companyEntity) {
+        return new CompanyCreateResponse(
+            companyEntity.getEmail(),
+            companyEntity.getName(),
+            companyEntity.getCeoName(),
+            companyEntity.getTelNumber()
+        );
+    }
+}
diff --git a/admin/src/main/java/kernel360/ckt/admin/service/CompanyService.java b/admin/src/main/java/kernel360/ckt/admin/service/CompanyService.java
new file mode 100644
index 00000000..3e15e691
--- /dev/null
+++ b/admin/src/main/java/kernel360/ckt/admin/service/CompanyService.java
@@ -0,0 +1,19 @@
+package kernel360.ckt.admin.service;
+
+import kernel360.ckt.admin.dto.request.CompanyCreateRequest;
+import kernel360.ckt.core.domain.entity.CompanyEntity;
+import kernel360.ckt.core.repository.CompanyRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class CompanyService {
+
+    private final CompanyRepository companyRepository;
+
+    public CompanyEntity create(CompanyCreateRequest companyCreateRequest) {
+        return companyRepository.save(companyCreateRequest.toEntity());
+    }
+
+}
diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml
new file mode 100644
index 00000000..f5b6c2a7
--- /dev/null
+++ b/admin/src/main/resources/application.yml
@@ -0,0 +1,17 @@
+spring:
+  application:
+    name: admin
+
+  datasource:
+    url: jdbc:mysql://${DB_URL}:${DB_PORT}/${DB_DATABASE}
+    username: ${DB_USERNAME}
+    password: ${DB_PASSWORD}
+  jpa:
+    hibernate:
+      ddl-auto: update
+    show-sql: true
+    hibernate.naming.physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+    open-in-view: false
+    properties.hibernate:
+      default_batch_fetch_size: 1000
+      globally_quoted_identifiers: true
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..152a2394
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,46 @@
+plugins {
+    id 'java'
+    id 'org.springframework.boot' version '3.3.1' apply false
+    id 'io.spring.dependency-management' version '1.1.7' apply false
+}
+
+allprojects {
+    group = 'kernel360.ckt'
+    version = '0.0.1-SNAPSHOT'
+
+    repositories {
+        mavenCentral()
+    }
+}
+
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'org.springframework.boot'
+    apply plugin: 'io.spring.dependency-management'
+
+    java {
+        toolchain {
+            languageVersion = JavaLanguageVersion.of(17)
+        }
+    }
+
+    configurations {
+        compileOnly {
+            extendsFrom annotationProcessor
+        }
+    }
+
+    dependencies {
+        // Lombok
+        compileOnly 'org.projectlombok:lombok'
+        annotationProcessor 'org.projectlombok:lombok'
+
+        // JUnit5
+        testImplementation 'org.springframework.boot:spring-boot-starter-test'
+        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+    }
+
+    tasks.named('test', Test) {
+        useJUnitPlatform()
+    }
+}
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 00000000..329e0736
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,8 @@
+dependencies {
+    // JPA
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+}
+
+tasks.bootJar {
+    enabled = false
+}
diff --git a/core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java b/core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
new file mode 100644
index 00000000..06b68a6e
--- /dev/null
+++ b/core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
@@ -0,0 +1,41 @@
+package kernel360.ckt.core.domain.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Builder
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor
+@Table(name = "company")
+@Entity
+public class CompanyEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(nullable = false, length = 50)
+    private String email;
+
+    @Column
+    private String password;
+
+    @Column(length = 50)
+    private String name;
+
+    @Column(length = 30)
+    private String ceoName;
+
+    @Column(length = 14)
+    private String telNumber;
+
+    @Column
+    private LocalDateTime createAt;
+
+    @Column
+    private LocalDateTime updateAt;
+
+}
diff --git a/core/src/main/java/kernel360/ckt/core/repository/CompanyRepository.java b/core/src/main/java/kernel360/ckt/core/repository/CompanyRepository.java
new file mode 100644
index 00000000..c4a97b86
--- /dev/null
+++ b/core/src/main/java/kernel360/ckt/core/repository/CompanyRepository.java
@@ -0,0 +1,9 @@
+package kernel360.ckt.core.repository;
+
+import kernel360.ckt.core.domain.entity.CompanyEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CompanyRepository extends JpaRepository<CompanyEntity, Long> {
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..8cdf200c
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,36 @@
+version: "3.8"
+services:
+  db:
+    image: mysql:8.0
+    environment:
+      MYSQL_DATABASE: ${DB_DATABASE}
+      MYSQL_USER: ${DB_USERNAME}
+      MYSQL_PASSWORD: ${DB_PASSWORD}
+      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+    ports:
+      - "3356:3306"
+    volumes:
+      - db_data:/var/lib/mysql
+    networks:
+      - ckt_network  # 네트워크 이름 변경
+
+  admin:
+    build:
+      context: ./admin
+    depends_on:
+      - db
+    environment:
+      SPRING_DATASOURCE_URL: jdbc:mysql://${DB_URL}:3306/${DB_DATABASE}
+      SPRING_DATASOURCE_USERNAME: ${DB_USERNAME}
+      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
+    ports:
+      - "8080:8080"
+    networks:
+      - ckt_network  # 네트워크 이름 변경
+
+volumes:
+  db_data:
+
+networks:
+  ckt_network:   # 네트워크 이름 변경
+    driver: bridge
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..249e5832
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..62e69c1c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed May 21 09:56:31 KST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..1b6c7873
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..107acd32
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..c26db134
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,4 @@
+rootProject.name = 'ckt-be'
+
+include 'core'
+include 'admin'


Reviews:
user: login: DongHyukki
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-22T12:34:40Z
user: login: maximum-zero
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T13:50:46Z
user: login: DongHyukki
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-23T01:19:33Z
user: login: DongHyukki
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-23T01:21:58Z
user: login: DongHyukki
body: 리뷰드린 내용 수정되면 머지하시면 될것같아요
state: APPROVED
author_association: NONE
submitted_at: 2025-05-23T01:26:07Z
user: login: jonathan0620
body: 환경별 DB 설정 분리 방식 참고 잘 했습니다!
.env 설정이랑 연동돼서 실제 적용할 때 실수 줄일 수 있겠네요
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T02:32:51Z
user: login: mannungman
body: 좋은 것 같습니다.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T02:33:09Z

Review comments:
diff_hunk: @@ -0,0 +1,42 @@
+package kernel360.ckt.core.domain.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@ToString
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
path: ckt-core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
user: login: DongHyukki
body: 관성적으로 다 붙이신거 같은데 개발하실때는 전부 떼놓고 필요한 케이스에만 붙이는게 좋습니다.
+ Entity 에 ToString 함부로 쓰면 연관관계 양방향이면 OOM 터집니다. 
created_at: 2025-05-22T12:34:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+package kernel360.ckt.core.domain.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@ToString
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
path: ckt-core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
user: login: maximum-zero
body: @DongHyukki  아하! ToString은 양방향 연관관계일때, 서로 재귀 루프에 빠져서 OOM이 발생하는건 처음 알았네요.

추가로
- `NoArgsConstructor`는 JPA가 DB에서 엔티티를 로드할 때 기본 생성자가 필요하다고 해서 추가한 내용이였습니다.
   - NoArgsConstructor(access = AccessLevel.PROTECTED) 혹은 직접 생성하는 편이 더 좋을까요?

- `AllArgsConstructor`는 Builder 패턴에 사용에 필요하다 하여 같이 들어간 내용이였습니다.

혹시 둘 다 생성자를 생성할 때 lombok이 아닌 직접 생성하는 편이 좀 더 안전할까요?
created_at: 2025-05-22T13:50:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+package kernel360.ckt.core.domain.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@ToString
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
path: ckt-core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
user: login: DongHyukki
body: @maximum-zero 
일단 Entity 에 Builder 가 필요한가에 대해 고민해보시면 좋을거 같아요.
보통 nullable 한 컬럼이 있기 마련이고 상황에 따라 구성되어야 하는 객체의 필드셋이 다를수 있습니다.
근데 builder 로 모두 필드를 열어버리게 되면, 얘기치 못한 객체의 생성이 되거든요. ( 즉 도메인 규칙이 쉽게 깨질 수 있음 )

간단한 예를 들면

객체에는 상태가 있죠.
그렇다면 그 객체가 생성이 되는 시점 ( 회사와 렌터카 회사가 계약이 된 시점 ) 에는 해당 회사의 상태는 반드시 Status = `CONTRACTED` 계약완료 된 상태로 객체가 생성이 되어야 합니다.

하지만 이때 Builder 로 필드가 열려있게 되면 Status 는 다양한 Status 로 객체 생성이 가능해 집니다.
created_at: 2025-05-23T01:19:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+package kernel360.ckt.core.domain.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@ToString
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
path: ckt-core/src/main/java/kernel360/ckt/core/domain/entity/CompanyEntity.java
user: login: DongHyukki
body: @maximum-zero 

저는 어떻게 해야 객체를 안전하게 서빙하고, 불변으로 안전하게 유지할 수 있을까 고민하다보면 결국 롬복과는 멀어지게 되더라구요.
하지만 이러한 코딩이 처음이시다보니 사용하시는것을 반대하는것은 아니고, 쓰더라도 이러한 상황들을 염두해두고 꼭 이러한 부분들까지도 고려할 수 있는 능력을 키우시면 좋을거 같습니다.
created_at: 2025-05-23T01:21:58Z
author_association: NONE

PR comments:
user: login: DongHyukki
created_at: 2025-05-22T12:32:24Z
body: @maximum-zero 

1. 모듈 이름에 붙어있는 `ckt` prefix 는 삭제하는게 좋을거 같습니다.
2. 어떤식으로 하시려는 지 제가 정확히 이해는 못한것 같은데, 다음과 같은 방식이 맞을까요?
- admin > core 방향으로 의존하고, admin 에서는 외부에 interface ( 자바 인터페이스 아님; controller 임 ) 만 제공하고 
core 에서 모든 유스케이스 ( service ) 처리를 담당

이 방식이 맞다면 admin 에서 JPA 를 의존하는 경우는 없을것 같고,

만약 이방식이 아니고, core 가 POJO 만 제공하는 경우라면 core 의 application.yaml 에 DB 설정 등을 넣지 않으시면 됩니다.  ( 넣지않아야 합니다 )

author_association: NONE
user: login: maximum-zero
created_at: 2025-05-22T14:01:32Z
body: @DongHyukki 
1. ckt는 제거하고 적용해보겠습니다.

2. 제가 설명이 좀 부족했던 것 같습니다.
   - 의도는 admin -> core 의존하는 부분은 일치하고, 
     `core` 에서는 `entity`, `vo` 와 같은 도메인 객체와 JPA의 `repository` interface 구성
     `admin` 에서는 `service`와 `controller` 및 `dto` 및 JPA 구현체 로 구성을 생각하고 있었습니다.
     
 3. `admin` 에서 실제로 scan을 통해 JPA 구현체가 생성되는 것으로 알고 있어 해당 부분에 application.yml 에 db 정보가 들어가고 있습니다.
 이 상황에서 `auth`라는 모듈이 추가되었을 때 auth -> core 의존하게 되고, `auth` 에서도 application.yml에 db 정보와 JPA 설정이 필요하게 되면 두 개의 application.yml 의 동일한 설정이 들어갈 것 같은데, 이 부분에서 설정을 실수로 변경하거나 잘못 설정하는 경우에 이슈가 생기지 않을까 하여 문의드린 내용입니다.
author_association: COLLABORATOR
user: login: DongHyukki
created_at: 2025-05-23T01:23:33Z
body: > @DongHyukki
> 
> 1. ckt는 제거하고 적용해보겠습니다.
> 2. 제가 설명이 좀 부족했던 것 같습니다.
>    
>    * 의도는 admin -> core 의존하는 부분은 일치하고,
>      `core` 에서는 `entity`, `vo` 와 같은 도메인 객체와 JPA의 `repository` interface 구성
>      `admin` 에서는 `service`와 `controller` 및 `dto` 및 JPA 구현체 로 구성을 생각하고 있었습니다.
> 3. `admin` 에서 실제로 scan을 통해 JPA 구현체가 생성되는 것으로 알고 있어 해당 부분에 application.yml 에 db 정보가 들어가고 있습니다.
>    이 상황에서 `auth`라는 모듈이 추가되었을 때 auth -> core 의존하게 되고, `auth` 에서도 application.yml에 db 정보와 JPA 설정이 필요하게 되면 두 개의 application.yml 의 동일한 설정이 들어갈 것 같은데, 이 부분에서 설정을 실수로 변경하거나 잘못 설정하는 경우에 이슈가 생기지 않을까 하여 문의드린 내용입니다.

@maximum-zero 
아하 오히려 프로젝트마다 다른 설정을 갖도록 하는게 더 유연한 방법입니다.
프로젝트마다 DB / JPA 설정들을 다르게 가져갈 수 있는데 이를 특정 모듈에 놓고 참조하는 방식으로 의존하게 되면 사용할수 있는 
기술/프레임워크/정책들이 고정되게 됩니다.
author_association: NONE
user: login: jonathan0620
created_at: 2025-05-23T02:30:30Z
body: 멀티모듈 구조 깔끔하게 잘 나눠주셔서 보기 너무 좋아요!
초기 세팅 하느라 고생 많으셨습니다. 덕분에 편하게 시작할 수 있을 것 같아요!
저는 전체적으로 만족스럽습니다. :)
author_association: COLLABORATOR