diff --git a/README.md b/README.md index 41da1ac..e9b8094 100644 --- a/README.md +++ b/README.md @@ -1 +1,39 @@ -# spring-sunshine-precourse \ No newline at end of file +# 날씨 조회 서비스 (Weather Service) + +## 기능 목록 +1. **도시 좌표 조회**: 입력된 도시 이름(Seoul, Tokyo, NewYork, Paris, London)에 해당하는 위도/경도를 조회한다. +2. **날씨 정보 조회**: Open-Meteo API를 사용하여 해당 좌표의 현재 날씨(온도, 체감 온도, 날씨 코드, 습도, 풍속)를 조회한다. +3. **날씨 요약 생성**: 조회된 날씨 정보를 바탕으로 사용자에게 친화적인 한 줄 요약 문장을 생성한다. +4. **API 응답**: 조회된 날씨 정보와 요약 문장을 JSON 형태로 반환한다. +5. **예외 처리**: 지원하지 않는 도시나 외부 API 오류 발생 시 정해진 포맷의 에러 응답을 반환한다. +6. **설정 외부화**: 도시 좌표 및 날씨 코드를 프로퍼티 파일로 분리하여 관리한다. +7. **회복 탄력성(Resilience)**: 외부 API 호출 시 타임아웃 및 재시도 기능을 통해 네트워크 불안정에 대비한다. + +## 구현 전략 +1. Jules, Codex 2개의 Sandboxed AI Coding Agent를 활용하여 초기 코드 개발 +2. guideline 파일을 통해 Backend Architecture, 개발 규칙 등을 사전에 정의하고 Agent가 이를 참조하여 개발 +3. 가능한 사전에 `build.gradle.kts`에 정의되어있던 의존성울 모두 사용하여 기능을 구현 +4. Agent가 개발한 코드를 검토하고 필요 시 리팩토링 수행 + +## 구현 상세 +* **아키텍처**: Domain Layer는 컴포넌트 기반, Service Layer가 Facade 역할로 조합하는 구조를 적용한다. + * `api`: 컨트롤러 및 요청/응답 DTO (검증 및 위임). + * `service`: Service Layer, Facade 역할로 여러 컴포넌트를 오케스트레이션. + * `component`: 단일 책임을 가진 작은 컴포넌트들 (좌표 매핑, 요약 생성 등). + * `domain`: 도메인 모델, 도메인 서비스. + * `repository`: 도메인 리포지토리 포트 정의. + * `infrastructure`: 외부 API 통신 (Open-Meteo Client) 등 기술 어댑터. +* **제약 사항 준수**: + * `else`, `switch`, `ternary operator` 사용 금지. + * 들여쓰기 2단계 제한. + * 메서드 길이 15줄 제한. + * Google Java Style Guide (4 spaces indent). +* **No-Else/Switch 전략**: + * 도시 매핑: `Map` 자료구조를 활용하여 조건문 없이 O(1) 조회. + * 날씨 코드 매핑: `Map` 활용. + * 방어적 코딩(Guard Clauses)을 통해 `else` 제거. +* **설정 관리**: + * `CityProperties`, `WeatherProperties` 클래스를 통해 외부 설정(YAML)을 객체로 바인딩하여 사용. +* **회복 탄력성**: + * `RestClient` 설정 시 `HttpClient` 타임아웃(Connect, Response) 적용. + * `ClientHttpRequestInterceptor`를 구현하여 실패 시 재시도 로직 적용 (Custom Retry Interceptor). diff --git a/build.gradle.kts b/build.gradle.kts index 3f75395..033c283 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,7 @@ plugins { id("org.springframework.boot") version "3.3.1" id("io.spring.dependency-management") version "1.1.5" - kotlin("plugin.jpa") version "1.9.24" - kotlin("jvm") version "1.9.24" - kotlin("plugin.spring") version "1.9.24" + id("java") } group = "camp.nextstep.edu" @@ -24,21 +22,14 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.springframework.boot:spring-boot-starter-reactor-netty") implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-mysql") - implementation("org.jetbrains.kotlin:kotlin-reflect") - runtimeOnly("com.h2database:h2") - runtimeOnly("com.mysql:mysql-connector-j") - testImplementation("org.springframework.boot:spring-boot-starter-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") -} -kotlin { - compilerOptions { - freeCompilerArgs.addAll("-Xjsr305=strict") - } + implementation("com.h2database:h2") + implementation("com.mysql:mysql-connector-j") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.junit.platform:junit-platform-launcher") } tasks.withType { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..8bdaf60 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f86..1af9e09 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..ef07e01 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 6689b85..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/prompt/commit-message-generation.md b/prompt/commit-message-generation.md new file mode 100644 index 0000000..f33300f --- /dev/null +++ b/prompt/commit-message-generation.md @@ -0,0 +1,252 @@ +Notice : Write Commit Message in korean + +Commit Message Conventions + +========================== +These rules are adopted from [the AngularJS commit conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/). + +* [Goals](#goals) +* [Generating CHANGELOG.md](#generating-changelogmd) + * [Recognizing unimportant commits](#recognizing-unimportant-commits) + * [Provide more information when browsing the history](#provide-more-information-when-browsing-the-history) +* [Format of the commit message](#format-of-the-commit-message) + * [Subject line](#subject-line) + * [Allowed ``](#allowed-type) + * [Allowed ``](#allowed-scope) + * [`` text](#subject-text) + * [Message body](#message-body) + * [Message footer](#message-footer) + * [Breaking changes](#breaking-changes) + * [Referencing issues](#referencing-issues) + * [Examples](#examples) + +Goals +----- +* allow generating CHANGELOG.md by script +* allow ignoring commits by git bisect (not important commits like formatting) +* provide better information when browsing the history + +Generating CHANGELOG.md +----------------------- +We use these three sections in changelog: new features, bug fixes, breaking changes. +This list could be generated by script when doing a release. Along with links to related commits. +Of course you can edit this change log before actual release, but it could generate the skeleton. + +List of all subjects (first lines in commit message) since last release: +```bash +git log HEAD --pretty=format:%s +``` + + +New features in this release +```bash +git log HEAD --grep feature +``` + +### Recognizing unimportant commits +These are formatting changes (adding/removing spaces/empty lines, indentation), missing semi colons, comments. So when you are looking for some change, you can ignore these commits - no logic change inside this commit. + +When bisecting, you can ignore these by: +```bash +git bisect skip $(git rev-list --grep irrelevant HEAD) +``` + +### Provide more information when browsing the history +This would add kinda “context” information. +Look at these messages (taken from last few angular’s commits): +* Fix small typo in docs widget (tutorial instructions) +* Fix test for scenario.Application - should remove old iframe +* docs - various doc fixes +* docs - stripping extra new lines +* Replaced double line break with single when text is fetched from Google +* Added support for properties in documentation + + +All of these messages try to specify where is the change. But they don’t share any convention... + +Look at these messages: +* fix comment stripping +* fixing broken links +* Bit of refactoring +* Check whether links do exist and throw exception +* Fix sitemap include (to work on case sensitive linux) + +Are you able to guess what’s inside ? These messages miss place specification... +So maybe something like parts of the code: docs, docs-parser, compiler, scenario-runner, … + +I know, you can find this information by checking which files had been changed, but that’s slow. And when looking in git history I can see all of us tries to specify the place, only missing the convention. + +--- + +Format of the commit message +---------------------------- +``` +(): + + + +