Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

202101969 송민주 #67

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
76 changes: 0 additions & 76 deletions .github/workflows/deployment.yml

This file was deleted.

34 changes: 34 additions & 0 deletions .github/workflows/run_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Deployment

on:
workflow_dispatch:
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Test with Gradle
run: ./gradlew test
39 changes: 3 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,6 @@
### Q. entity와 model은 왜 나뉘어 있나요?
PostRequest와 Post가 유사하게 생겼는데 클라이언트로부터 Post객체 그대로 요청 받아서 Post를 그대로 저장하면 편하고 관리할 클래스도 적어지지 않을까요?
### A. Entity와 DTO를 구분하기 위함입니다.
우선 Entity와 DTO의 정의부터 살펴보겠습니다.
### 실전 코딩 테스트
- 코딩 과정 중 테스트
- 코딩 이외의 테스트

**Entity**는 DB 테이블에 존재하는 컬럼들을 필드로 가지는 객체를 뜻합니다. (즉, Entity는 DB 테이블 설계와 동일한 모습을 하고있겠죠.)
Entity는 DB 테이블과 1:1 대응이며, 테이블을 가지지 않는 컬럼을 필드로 갖지 않습니다.

**DTO**는 Data Transfer Object의 준말로 데이터가 계층을 오갈 때(transfer) 사용하는 객체입니다. DTO는 로직을 갖지 않는 순수한 데이터 객체이며,
getter/setter 메서드만을 가집니다.

Entity와 DTO를 구분지어 사용하는 이유는 '각 레이어간의 의존도를 줄이기 위함'입니다. 더 쉽게 예시를 들어보면 아래와 같습니다.
1. Post와 Tag를 각각 저장하도록 테이블을 별도로 설계했다고 해봅시다.
* 현재 우리 코드에서는 Post에 Tag를 같이 저장하도록 했지만, Tag의 내용이 많아 Post와 Tag를 각각 저장하도록 설계를 변경했습니다.
Post와 Tag의 테이블은 분리했지만 보통 우리가 게시글을 작성할 때 태그도 함께 작성하니 Request Body도 Post와 Tag를 한 번에 받아야 좋겠죠.
Post 클래스 하나로 요청과 DB 저장을 모두 처리했다면 위와 같은 상황 대처가 어려웠을텐데 Post와 PostRequest가 나뉘어 있으니 요청은 현재 PostRequest가 받던대로 게시글 내용과 Tag를 함께 받고
저장할 때 Post와 Tag 엔티티로 저장하면 위와 같은 상황에 대처가 가능해집니다.
2. 기획자가 게시글 작성과 관련한 기획을 변경한 상황을 가정해봅시다.
* 기능의 요구사항은 언제든 추가/변경/삭제가 생깁니다. 그럼 요청을 받아야하는 객체의 필드에도 추가/변경/삭제가 언제든 발생할 수 있습니다.
PostRequest가 없었다면, Post 클래스의 필드가 수시로 변경되어 DB에 데이터를 저장할 때 장애가 발생하기 쉽습니다. (없는 필드를 저장하려고 한다던지 등등)

---

### Q. WebMVCConfig 파일은 왜 있나요?
### A. CORS을 허용하기 위해서입니다.
http://localhost:8080 이라는 url이 있다고 합시다.

Origin은 host부분인 localhost와 포트인 8080을 모두 합친 것을 말합니다. http://localhost:9090은 포트가 다르니 Origin이 같다고 할 수 없죠.
서버가 이미 http://localhost:8080에서 돌고 있으니, 프론트 프로그램은 8080포트가 아닌 다른 포트(예를들면 9090)에서 돌게 됩니다.
브라우저는 보안상의 이유로 SOP(Same Origin Policy) 정책을 요구하고 있습니다. 프론트에서 api를 통해 정보를 얻어왔지만,
브라우저가 정보를 요청한 쪽(http://localhost:9090)과 정보를 준 쪽(http://localhost:8080)의 Origin이 달라 서버로부터 받아온 응답을 거절합니다.

하지만 위처럼 서로 다른 Origin끼리 데이터를 주고 받아야하는 상황이 발생하기 때문에 별도의 예외 사항을 두었습니다. CORS가 바로 이 예외상황에 해당합니다.
CORS(Cross Origin Resource Sharing)는 다른 Origin으로 요청을 보내기 위해 지켜야하는 정책으로, 원래대로라면 SOP에 의해 막히게 될 요청을 풀어주는 정책입니다.
WebMvcConfig에서 CORS를 위해 추가한 파일이라고 알아주시면 될 것 같습니다!(프론트 수업을 위해 추가한 설정파일) - ❗️프론트엔드 수업하려면 해당 파일이 필수이니 프론트엔드 수업 전까지 반드시 pull하기❗️

글솜씨가 없어 간단히 적었지만, 토이프로젝트를 하다보면 정말 자주 마주치는 문제 중 하나가 CORS입니다!!! 기본적인 지식이기도 하니 시간되실 때 아래의 블로그를 읽어보셔도 좋을 것 같습니다

📌 https://it-eldorado.tistory.com/163
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.junit.jupiter:junit-jupiter:5.8.1'
testImplementation 'org.projectlombok:lombok:1.18.22'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' //TODO: annotation build 활성화 언급하기
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
Expand All @@ -36,3 +38,9 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

test {
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/cnu/real_coding_server/error/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.cnu.real_coding_server.error;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
String code; // 클라이언트랑 약속한 작동
String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.cnu.real_coding_server.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class RealCodingExceptionHandler {
@ExceptionHandler(SlangBadRequestException.class)
public ResponseEntity<ErrorResponse> handleSlangRequestException(SlangBadRequestException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getErrorResponse());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.cnu.real_coding_server.error;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class SlangBadRequestException extends RuntimeException {
private static final long serialVersionUID = -4785136912743477236L;

public SlangBadRequestException() {
super("비속어가 포함된 글은 등록할 수 없습니다");
}

public ErrorResponse getErrorResponse() {
return new ErrorResponse("slangInput", this.getMessage());
}
}
19 changes: 16 additions & 3 deletions src/main/java/com/cnu/real_coding_server/service/PostService.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
package com.cnu.real_coding_server.service;

import com.cnu.real_coding_server.entity.Post;
import com.cnu.real_coding_server.error.SlangBadRequestException;
import com.cnu.real_coding_server.model.request.PostRequest;
import com.cnu.real_coding_server.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import com.cnu.real_coding_server.service.valid.PostValidService;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class PostService {

private final PostRepository postRepository;
private static final List<String> slangList = List.of("비속어1", "비속어2");
private final PostValidService postValidService;

public Post createPost(PostRequest postRequest) {

if (postValidService.isSlangInclude(slangList, postRequest.getTitle(), postRequest.getContents())) {
throw new SlangBadRequestException();
}
log.info("정상 저장 확인");
return postRepository.save(postRequest.toEntity());
}

Expand All @@ -28,6 +38,9 @@ public Optional<Post> getPost(Integer postId) {
}

public Optional<Post> updatePost(Integer postId, PostRequest postRequest) {
if (postValidService.isSlangInclude(slangList, postRequest.getTitle(), postRequest.getContents())) {
throw new SlangBadRequestException();
}
return postRepository.findById(postId)
.map(post -> {
post.setTitle(postRequest.getTitle());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.cnu.real_coding_server.service.valid;

import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class PostValidService {
public boolean isSlangInclude(List<String> slangList,
String title,
String postContent) {
for (String slang : slangList) {
if(title.contains(slang)
|| postContent.contains(slang)) {
return true;
}
}
return false;
}
}
25 changes: 25 additions & 0 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
spring:
# H2 Setting Info (H2 Console? ???? ?? ???? ??)
h2:
console:
enabled: true # H2 Console? ???? ?? (H2 Console? H2 Database? UI? ????? ??)
path: /h2-console # H2 Console? Path
# Database Setting Info (Database? H2? ???? ?? H2?? ?? ??)
datasource:
driver-class-name: org.h2.Driver # Database? H2? ?????.
url: jdbc:h2:file:~/demodb # H2 ?? ??
username: sa # H2 ?? ? ??? username ?? (??? ??? ??)
password: # H2 ?? ? ??? password ?? (??? ??? ??)

jpa:
hibernate:
ddl-auto: none # ??????? ??? ? ??????? ????? ?? ??? ??
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
properties:
hibernate:
format_sql: true # ???? query? ???

logging.level:
org.hibernate.SQL: debug
22 changes: 22 additions & 0 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
spring:
# H2 Setting Info (H2 Console? ???? ?? ???? ??)
h2:
console:
enabled: true # H2 Console? ???? ?? (H2 Console? H2 Database? UI? ????? ??)
path: /h2-console # H2 Console? Path
# Database Setting Info (Database? H2? ???? ?? H2?? ?? ??)
datasource:
driver-class-name: org.h2.Driver # Database? H2? ?????.
url: jdbc:h2:mem:devblog # H2 ?? ??
username: sa # H2 ?? ? ??? username ?? (??? ??? ??)
password: # H2 ?? ? ??? password ?? (??? ??? ??)

jpa:
hibernate:
ddl-auto: create-drop # ??????? ??? ? ??????? ????? ?? ??? ??
properties:
hibernate:
format_sql: true # ???? query? ???

logging.level:
org.hibernate.SQL: debug
4 changes: 3 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ spring:

jpa:
hibernate:
ddl-auto: create # ??????? ??? ? ??????? ????? ?? ??? ??
ddl-auto: none # ??????? ??? ? ??????? ????? ?? ??? ??

properties:
hibernate:
format_sql: true # ???? query? ???
open-in-view: false

logging.level:
org.hibernate.SQL: debug
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.cnu.real_coding_server.service.week1.base.service;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class PostBaseServiceTest {


@DisplayName("테스트 코드 실행해보기")
@Test
void testDoCode() {
System.out.println("테스트 코드 실행!");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.cnu.real_coding_server.service.week1.base.service;


import com.cnu.real_coding_server.entity.Post;
import com.cnu.real_coding_server.service.PostService;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.assertj.core.api.Assertions.assertThat;

class PostServiceTest {



@DisplayName("테스트 코드 실행해보기")
@Test
void testDoCode() {
System.out.println("테스트 코드 실행!");
}

}
Loading