개괄

드디어 주특기 주차로 접어들었다. 가장 먼저 한 것은 JPA를 활용해 웹종때 수강했던 간단한 post를 CRUD하는 subject를 spring을 이용해 재 구현해 보았다. 그리고는 Spring Boot Framework의 원리에 대해 수강했다.

 

목표

 

배운것 - TIL SERIES 

스프링 프레임워크의 간단 정리

 

스프링 프레임워크 시리즈

 

왜?라는 물음은 왜 중요할까?

 

스프링 프레임워크의 특징 3가지에는 어떤 것이 있을까?

너무 많은 특징 중 3개정도만 간추려 보았다.

  1.  ApplicationContext
    1. 스프링은 ApplicationContext라는 IOC 컨테이너를 사용합니다. 이 컨테이너 덕분에 우리는 모든 API에 대해 설정 구성을 일일히 해 주지 않아도 됩니다. 또, 빈의 생성이나 관계를 설정해 주는 일에 대해 해방될 수 있고, 결국 과거에 위와 같은 코드를 짰던 시간만큼 조금 더 빠른 웹 어플리케이션을 만들 수 있습니다. 또한 확장성도 높아집니다.
  2. DI
    1. DI는 IOC와 함께 스프링의 핵심 개념 중 하나입니다. 세션에서 배웠듯 우리가 singleton 패턴을 사용해 stateless하게 설계해야하는 것들이 많습니다. 그러나 우리가 배운 SOLID 원칙을 지키려고 하거나 DI없이 api를 설계할 때면, 이러한 원칙들이 부딪히는 경우가 종종 등장합니다. DI는 이것들을 해결해 줍니다.
  3. 영속성과 관련된 다양한 인터페이스
    1. 스프링은 영속성과 관련된 다양한 라이브러리를 지원합니다. 데이터베이스의 가장 핵심적인 원칙은 ‘무결성’이라고 배웠습니다. 그 말마따나 데이터베이스에는 아무 데이터나 집어넣으면 안됩니다. 그러나 스프링은 자바의 (때로는 너무 깐깐한) 데이터 타입의 이점을 십분 활용해서, 그에 맞게 개발된 다양한 라이브러리를 제공하여 데이터베이스와 관련된 행위들을 사용자가 안심하고 진행할 수 있게 합니다.

스프링에서 DI (의존성 주입) 를 사용하는 이유가 무엇일까?

항해와 뗄레야 뗄 수 없는 기술매니저, 담임매니저로 예를 들어보겠습니다.

우리 C반이 하는 모든 작업을 코드로 작성한다고 가정했을 때,

우리 11 → 9조는 이번 주를 김태현 기술매니저님, 송민진 담임매니저님과 함께 하고 있죠?

그럼 상담, 시험, 공지안내 등은 송민진 인스턴스를 생성해서 코드를 작성해야 할 것이고

또 기술매니저 순회등은 김태현 인스턴스를 생성해서 코드를 작성해야 할 것입니다.

만약 9조 주차별 조 평가형식이 있다면 송민진 인스턴스와 김태현 인스턴스를 둘 다 생성해서 코드를 작성해야 합니다.

그런데 만약 송민진 매니저님이 아침에 저를 깨우다 지쳐서 담임매니저를 때려친다면?

우리가 작성한 모든 코드에서 송민진 매니저님을 지우고 새로운 매니저 인스턴스를 생성해주는 코드로 바꿔야 할 것입니다.

또 만약 다음 주차로 가서 김태현 매니저님이 아닌, 진유진 기술매니저님으로 바뀐다면?

수많은 코드들을 매주 지우고 매주 새로 짜야 할 것입니다.

그러나 인스턴스 생성 방식이 아닌, 매니저 인터페이스를 주입받고 맨 마지막에

담임매니저 = 송민진, 기술매니저 = 김태현으로 구체화해 준다면?

아 그냥 상상만 해도 누가 매니저하든 상관이 없을 것입니다.(제 의견 아님)

그래서 벌써 이렇게만 해도 두 가지 이점이 생깁니다.

  1. 수많은 코드를 덜 교체해도 됨
  2. 코드를 처음 보는 사람은 누가 기술매니저인지 누가 운영매니저인지 뭔 역할을 하는지 알수가없음

ORM, JPA, 그리고 Spring Data JPA

**ORM**은 객체(Instance)와 관계형 데이터베이스(RDBMS)의 데이터를 자동으로 매핑해주는것을 말합니다. 서블릿 컨테이너가 Web Request와 Servlet Component를 자동으로 매핑해주듯이요. 그래서 Object Relational Mapping입니다.

근데 생각해보니 이상하지 않나요?

Web Request가 “localhost:8080/api/hanghae”로 GET 메서드를 통과한다 치면 서블릿 컨테이너가 어 그래 /api/hanghae구나 어 그래 GET Method구나 해서 연결시켜주면 된다고 치고 (요청과 응답의 url type은 String으로 동일하니까)

@RestController
@RequestMapping("/api")
public class HangHaeController{
	@GetMapping("/hanghae")
	public String cry(){
			return "응애"
		}
}

우리는 자바 객체를 쓰니까 class를 사용하고, RDBMS는 table을 사용하는데(객체 모델과 관계형 모델 간의 불일치) 이 친구가 어떻게 알고 맵핑을 시켜주는 걸까요?

**JPA**라는 친구가 무한SQL 마법을 써서 알아서 해주는 거랍니다.

JPA 안의 JDBC API라는 친구가 SQL문을 써서 알아서 DB에 접근을 해주게요.

우리는 이제 JOIN문을 안쓰고도 관계 Annotation만 써주면 JPA가 알아서 이 문제를 해결 해 줍니다.

JPA의 장점이 뭘까요? 며칠전에 in-line memory를 써서 h2 데이터베이스로 데이터를 삽입해줬습니다.

근데 문제점이 뭐냐 : 끄면 내 데이터 다 사라짐, 누가 내 데이터 저장된거 보여주세요 하면 다시 SQL문써서 데이터 만들고 JOIN 쿼리로 데이터베이스의 table key값 연동시켜주는 수고를 해야함

그래서 h2같은 데이터베이스가 종료되고 걍 컴 껐다키고 U+가 내 인터넷을 조졌다가 다시 연결시켜줘도 알아서 관계가 맺어질 수 있도록 객체-테이블에 매핑을 해 놓은것입니다.

⇒ 그래서 휘발성이 아닌 영속성(Persistant) 이라는 말을 쓰고, 이걸 자바에서 제공한다고 해서

Java Persistant Api라는 단어가 나오게 된거죠.

또 여기 JPA와 다른 ORM인 MyBatis의 비교도 있습니다.

그럼 **Spring Data JPA**란 무엇일까요?

아까 위에서 제가 스프링 프레임워크의 장점에 대해서 작성할 때, 스프링 프레임워크는 영속성 데이터베이스와 관련된 다양한 인터페이스와 라이브러리를 지원하는 장점이 있다고 썼습니다.

이 Spring Data JPA는 JPA와 관련된 스프링에서 제공하는 모듈입니다.

이번주차 항해 인강을 들을 때, 요청이 들어오면 Entity Manager Factory라는곳에서 노예(EntityManager)들을 만들어서 이 친구들이 DB connection pool에 접근해서 DB를 건드린다는 설명을 들으셨을 겁니다.

그런데 우리가 지난주차에 숙제로 제출한 코드를 봅시다.

<Repository>

public interface LetterRepository extends JpaRepository<Letter, Long> {
    List<Letter> findAllByOrderByModifiedAtDesc();
}

<Service>

@Service
@RequiredArgsConstructor
public class LetterService {
    private final LetterRepository letterRepository;
    @Transactional(readOnly = true)
    public List<LetterResponseDto> findAll(){ // service Layer에서 Entity >> DTO 변환작업, 사유 : LazyInitializationException 위험부담 줄임
        return letterRepository.findAllByOrderByModifiedAtDesc().stream().map(LetterResponseDto::new).collect(Collectors.toList());
    }
}

우리는 EntityManager를 쓴 기억조차 없죠. 왜일까요? 우리가 Extend한 JpaRepository에 그 답이 있습니다.

package org.springframework.data.jpa.repository.support;

import ...

public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

    private final EntityManager em;

    public Optional<T> findById(ID id) {

        Assert.notNull(id, ID_MUST_NOT_BE_NULL);

        Class<T> domainType = getDomainClass();

        if (metadata == null) {
            return Optional.ofNullable(em.find(domainType, id));
        }

        LockModeType type = metadata.getLockModeType();

        Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

        return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
    }

    // Other methods...
}

JPA Repository에서 이런 엔티티 코드를 다 만들어놓고 우리에게 적당한 타입과 ID만 넣어 주면 이 메소드를 자동으로 오버라이딩하게 만들어놔서 그렇습니다.

Spring Data JPA가 없었다면? 우리는 저 한줄로 끝낸 findAll 메소드를 다음과 같이 써야 할 것입니다.

public abstract class GenericDAOImpl<T, id> implements GenericDAO<T, id> {

private Class<T> type;

@PersistenceContext
protected EntityManager entityManager;

public GenericDAOImpl(Class<T> type) {
    super();
    this.type = type;
}

... save and delete classes go here

@Override
public List<T> findAll(T2 where) {

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
    Root<T> rootQuery = criteriaQuery.from(type);
    if (where != null) {

        EntityType<T> entity = entityManager.getMetamodel().entity(type);

        SingularAttribute<? super T, ?> attribute = null;
        for (SingularAttribute<? super T, ?> singleAttribute: entity.getSingularAttributes()) {
            // loop through all attributes that match this class
            if (singleAttribute.getJavaType().equals(where.getClass())) {
                // winner!
                attribute = singleAttribute;
                break;
            }
        }
        // where t.object = object.getID()
        criteriaQuery.where(criteriaBuilder.equal(rootQuery.get(attribute), where));
    }
    criteriaQuery.select(rootQuery);
    TypedQuery<T> query = entityManager.createQuery(criteriaQuery);

    // need this to make sure we have a clean list?
    // entityManager.clear();
    return query.getResultList();
}

결론 : Spring Data JPA 쓰자

진짜 마지막

 

'웹개발' 카테고리의 다른 글

TIL - 스프링 부트와 리액트의 협업 "Hello Meohyun!"  (1) 2023.02.01

개괄

이번 개인 과제는 JPA를 사용해서 게시판을 만들어보는 과정이었다. 무슨 문제가 있었는지 확인해보고, 해결해보자.

사실은 게시판이라기보다는 거의 뭐 쪽지 짬통 테스트같은 느낌이다.

 

github

https://github.com/choincnp/SpringBoard

 

GitHub - choincnp/SpringBoard

Contribute to choincnp/SpringBoard development by creating an account on GitHub.

github.com

 

설계 방식

1. 요구사항 받아들이기

2. API 명세서 설계하기

3. 기능명세서 설계하기

4. 디렉토리 분리하기

 

개발 flow

1. entity를 먼저 만든다.

2. 그 entity를 담을 Repository를 만든다.

3. 2번에서 만든 Repository를 Controll할 Controller를 만든다.

4. Controller에서 받을 RequestDto를 만들기

5. 이제 받을 RequestDto를 핵심 로직으로 조질 Service를 설계한다.

6. 핵심 로직을 거쳐서 return해줄 ResponseDto를 설계한다.

 

여기서 문제 : 왜 Entity를 Dto에 담아서 return해줘야 하는가?

간단히 말하면, Entity의 모든 정보를 담아서 return해줄 필요는 없기 때문이다.

Board의 Entity 안에는 많은 정보가 들어간다. PK인 id값을 포함해 제목, 내용, 작성자 등도 들어가지만, 가장 중요한 게시물을 지울 수 있는 password값이 들어가있다. password값까지 response로 리턴해줄 필요도 없고, 또한 누가 접근했을 때 한단계 더 숨겨줄 수 있기 때문이다.(Encapsulism과 비슷한 맥락이라고 보면 된다.)

이 Letter를 list로 만들어서 반환하는 것이 아니라, 

이렇게 ResponseDto에 담아 주는것이 현명하다.

그럼 Letter를 ResponseDto로 바꿔 주어야 하는데, 어떻게, 또 어느 Layer에서 변환을 해 주어야 할까?

Controller Layer에서 변환해주어도 괜찮지만, 보통은 Service Layer에서 변환작업을 해 주는게 좋기도 하고, flow에 맞다.

 

그럼 Entity를 DTO로 어떻게 바꿔줄까?

내가 알아본 방법은 두 가지였다.

1. Stream을 이용해 바꿔주기

2. Array로 만들고 List로 바꿔주기

그 중 Stream을 이용하기로 했다.

이렇게 자바8의 collection을 사용해서 바꾸어 주었다.

LetterController

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class MainController {

    private final LetterService letterService;

    @GetMapping("/posts")
    public List<LetterResponseDto> viewAll(){
        return letterService.findAll();
    }

    @PostMapping("/post")
    public Long postLetter(@RequestBody LetterRequestDto letterRequestDto){
        return letterService.postLetter(letterRequestDto);
    }

}

여기서 @RestController란 무엇일까?

@RestController = @Controller + @ResponseBody이다.

@Controller 어노테이션은 기존의 컨트롤러 어노테이션과 똑같고,

@ResponseBody 어노테이션은 return해주는 객체를 MessageConverter를 통해 매핑해서 본문에 담아 클라이언트에 전달한다.

 

처음에는 RequestBody와 헷갈려서 그대로 전달했다가 에러가 나버렸다.

그래서 Controller에 @RequestBody를 다 붙여주었다.

그랬더니 또 문제가 났다.

api 명세서에는 password만 들고 들어와야 하는 것이다.

보통 우리가 게시판에서 글을 지우려면 pop-up창에 password 하나만 치면 삭제가 되는 구조를 많이 이용한다.

그래서 이 password값만 받아오고 싶은데, 도저히 RequestDto에 있는 password값을 가져와서 다시 확인하기는 싫었다.

나중에 상세 페이지에 들어가서 삭제를 누르려면 RequestDto를 이용하면 되겠지만, 상세 페이지 밖에서 글을 삭제하려면

RequestDto에 있는 타이틀, 제목같은 not null값을 다 채워줘야 하기 때문이다.

 

한참을 500에러와 씨름하다가 다음과 같은 깨달음을 얻었다.

@RequestBody

RequestBody는 요청값을 json type로 받겠다고 선언하는 어노테이션으로, 이 어노테이션 덕분에 우리는 DTO 형태로 값을 가져올 수 있다. Spring은 Controller로 요청을 받을 때 jackson이라는 내장 라이브러리에 있는 MappingJackon2HttpMessageConverter를 이용해 요청값으로 들어온 json형태의 데이터를 DTO에 맞는 형태로 파싱해주게 된다. 그래서 우리는 스프링을 통해 json 형태를 편리하게 DTO라는 객체로 받아올 수 있는 것이다.

그래서 이 JSON 형태의 글이 @Responsebody 어노테이션을 통해

RequestDto requestDto = new RequestDto(title,content,author,password)

형태로 자동 바인딩되어 저장된다.

이를 "역직렬화 / DeSerialization"이라고 부른다! (Singleton패턴에서 왜 이런 개사기스킬을 만들어놨는지 궁금했는데 여기서 깨달았다.)

역직렬화는 생성자를 거치지 않고, Java의 Reflection을 통해 객체를 정의하는 매커니즘이다. 직렬화 가능한 클래스들은 기본 생성자가 항상 있어야 하기 때문에 RequestDto에 기본 생성자가 정의되어있지 않으면 데이터 바인딩에 실패한다.

 

아무튼 나는

@DeleteMapping("/post/{id}") //RequestBody 방식으로 구현, body : raw - text
    public String deleteLetter(@PathVariable Long id, @RequestBody String password) throws JSONException {
        return letterService.deleteLetter(id, password);
    }

이렇게 RequestBody 어노테이션 뒤에 String password를 선언하였기 때문에 JSON타입의 password가 내가 생각한 JSON객체로 들어오지않고

String password = “{\“password\“:\“1234\“}”;

와 같은 형태로 들어왔던 것이다.

 

그럼 어떻게 하는데!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

방법은 3개가 있다.

1. 죽어도 RequestBody를 쓰고 싶으면 @RequestBody JSONObject password로 받기

2. @ModelAttribute를 쓰기 : ModelAttribute는 RequestBody나 RequestParam을 선언하지 않으면 디폴트로 들어가기 때문에

    @DeleteMapping("/post/{id}") //ModelAttribute 방식으로 구현, body : x-www.form-urlencoded
    public String deleteLetter(@PathVariable Long id, String password) throws JSONException {
        return letterService.deleteLetter(id, password);
    }

이렇게 선언해서 나중에 body타입으로 받아주기

3. @RequestParam 사용하기 : RequestParam은 1개의 parameter를 받기 위해 사용되고있다. null 들어가면 안된다.

    @DeleteMapping("/post/{id}") //REQUESTPARAM 방식으로 구현, body : form-data
    public String deleteLetter(@PathVariable Long id, @RequestParam String password, Model m) throws JSONException {
        m.addAttribute("password",password);
        return letterService.deleteLetter(id, password);
    }

 

느낀점

정말 맛있는 과제였다. 덕분에 정말 HTTP request, response, 그리고 MVC패턴과 각종 어노테이션에 대해서 학습할수 있는 좋은 기회였다....

 

앞으로 남은 과제는 유효성 처리를 어느 Layer에서 할까에 대해 고민해보아야겠다.

 

 

 

 

https://velog.io/@jsb100800/Spring-boot-DTO-Entity-간-변환-어느-Layer에서-하는게-좋을까

 

[Spring boot] DTO <-> Entity 간 변환, 어느 Layer에서 하는게 좋을까?

Spring Boot 개발 중 학습이 필요한 내용을 정리하고,트러블 슈팅 과정을 기록하는 포스팅입니다.

velog.io

https://thalals.tistory.com/230

 

[Spring] JPA FindAll<Entity> to convert DTO (ModelMapper)

Spring에서는 DTO를 이용해서 사용자에게 Request를 받고, Response를 보냄으로써, 데이터를 보호한다. Request를 DTO로 받는방법은 정말 많고, 자료도 많지만 Response를 DTO로 변환하는 방법은 잘 나오지 않

thalals.tistory.com

https://frogand.tistory.com/163

 

[Spring] @RequestBody vs @ModelAttribute

이전 글 https://frogand.tistory.com/114 [Spring] @RequestBody, @RequestParam, @ModelAttribute의 차이 Client에서 받은 요청을 객체로 바인딩하기 위해 사용하는 방법에는 총 @RequestBody, @RequestParam, @ModelAttribute 총 3가지

frogand.tistory.com

https://mangkyu.tistory.com/72

 

[Spring] @RequestBody, @ModelAttribute, @RequestParam의 차이

이번에는 Spring에서 Client로 받은 요청을 객체로 바인딩하기 위해 사용하는 방법들에 대해서 알아보도록 하겠습니다. 1. RequestBody, ModelAttribute, RequestParam이란? [ @RequestParam ] @RequestParam은 1개의 HTTP

mangkyu.tistory.com

 

개괄

3주차를 시작하기에 앞서서 다음과 같은 과제의 제출을 요구받았다.

첫 번째는 BUS 클래스를 모델링하고, 두번째는 Requirements를 만족하는 BUS 클래스를 리팩터링 하는 것이다.

문제 발생

문제를 해결하는것은 그렇게 어렵지 않지만, 요구사항을 맞추면서 미래의 요구사항을 생각해서 만드는 '설계'가 쉽지 않았다. BUS 클래스는 어떻게 만들어야 할까? 그리고 BUS객체를 생성해주는 팩토리 메서드는 어떻게 생성해야 할까?

https://github.com/choincnp/Hanghae_4thweek

 

GitHub - choincnp/Hanghae_4thweek

Contribute to choincnp/Hanghae_4thweek development by creating an account on GitHub.

github.com

 

시도해본 것들

팩토리 메서드 패턴이란?

"구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다."

다양한 구현체(Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.

OCP와 비슷한 맥락이다. 최대한 메서드를 팩토리 인터페이스에 빼놓고 구체적인 것만 하위 모듈에서 정한다.

핵심 : 새로운 클래스를 만들 때 기존 코드를 건드리냐 아니냐가 핵심

복습 :

팩토리 메소드 패턴을 적용했을때의 장점, 단점

  • 장점
    • OCP를 잘 지킬 수 있다. 객체가 추가되어도 if-else등의 구문에서 자유롭다.
    • 코드를 간결하게 구현할 수 있다.
  • 단점
    • 클래스가 너무 많이 늘어난다.

여기서 OCP란 무엇일까?

  • SRP와 마찬가지로 기존 클래스에서 어떤 클래스를 수정해야 할 때 기존 클래스를 건드리지 않고 새로운 인스턴스를 다른 방법으로 확장할 수 있어야 한다.
  • 새로운 클래스를 설계할때는 확장성이 있어야 한다.
  • 나중에 설명할 때 “무엇”이 확장이고 “무엇”이 변경인지 말해줘야 함.

DIAGRAM

설계한 것을의 Diagram을 뽑아내면 다음과 같다.

원래는 자바 11 위에서는 interface 안에 default modifier를 사용해 구현된 메서드를 넣을 수 있지만,

내가 아직 배우기로는 "인터페이스 안 모든 메서드는 public abstract여야 한다"이기 때문에, 팩토리 인터페이스 안에 모든것을 넣기보다는, 하위의 busfactory라는 추상 클래스에 의존하도록 설정했다.

나중에 혹시나 대형버스 / 소형버스별로 다르게 생성할 수 있어야 해서

그 busfactory를 상속받는 LargeBusFactory와 SmallBusFactory로 나눠 구현했다.

Bus 인스턴스와 Taxi 인스턴스는 Vehicle을 상속받으며, 위의 요구사항을 맞춰 설계했다.

버스의 상태는 Enum으로 운행중과 차고지행으로 나누었다.

 

가장 고민한 부분은, 요구사항 중 버스 객체는 고유값으로 설정되어야 하는데, 큰 버스와 작은 버스의 고유값은 겹치지 않게끔 해야하는 것이었다.

 

그래서

BusFactory 안에 busNum을 설정해두고,

BusFactory를 상속받는 하위 XXXBusFactory에 sequence를 설정해 두었다.

사실 이렇게 쓰고보니 꽤 간단한 일이었는데, 당시에는 정말 고민을 많이 했다.

 

팩토리 메서드의 단점으로 많은 클래스를 꼽았는데, 디렉토리로 나눠두면 오히려 더 깔끔하고 좋을 것 같다.

개괄

스프링 컨테이너는 자바에서만으로는 지키기 어려운 OCP와 DIP를 지킬 수 있게끔 도와준다.

하지만 OCP, DIP가 무엇인지 SOLID에서 깨닫지 못하면 말짱 도루묵이 된다.

또, SOLID를 이해하고 적용하기 위해선 상속과 인터페이스에 대한 이해가 있어야 한다.

상속과 인터페이스란 무엇일까?

 

목표

상속과 인터페이스를 알고, 설계의 핵심 원리에 다가갈 수 있게끔 한걸음 나아가보자.

 

배운 것 - TIL SERIES(LINK)

붕어빵 장사와 함께하는 클래스, 메서드, 추상 클래스와 인터페이스

 

 

팩토리 메서드 디자인 패턴과 함께하는 버스 공장 만들어보기

https://choincnp.tistory.com/44

 

TIL - 팩토리 메서드 디자인 패턴과 함께하는 버스 공장 만들어보기

개괄 3주차를 시작하기에 앞서서 다음과 같은 과제의 제출을 요구받았다. 첫 번째는 BUS 클래스를 모델링하고, 두번째는 Requirements를 만족하는 BUS 클래스를 리팩터링 하는 것이다. 문제 발생 문제

choincnp.tistory.com

 

목표 달성 여부

저번주와는 다르게, 이번주는 조금이나마 객체지향이 무엇인지를 알 수 있었다.

물론 개념은 디스크 조각 모음처럼 내 머리에 저장되고 있지만 이번주는 그 조각이 조금씩 연결되는 것 같아 기뻤다.

많은 것을 공부하지는 못했지만, 그래도 이렇게만 하면 언젠가는 발전할 수 있을것이라는 기대를 가졌다.

 

느낀 점

늘 그랬듯, 설계를 한다는 것은 내 눈앞에 보이는 것이 아니라 숲을 미리 보고 작성하는 것이고,

더 나아가서 보이지 않는 미래를 보며 꿈꾸고 꾸미는 것이 설계이다.

그래서 좋은 설계는 추상적이어야 하고, 추상적인 것에 의지해야 한다.

'인간 군상'이라는 말이 있듯, 클라이언트는 언제 어떤 요구조건을 들이밀 지 모른다.

그것이 가능하든, 불가능하든 최대한 생각해서 프로그래밍을 해야 한다.

 

다음 주 목표

누구나 인강을 들으면 '아~이거구나'라고 이해할 수 있다. 이해를 하지 못해도, 이해를 해도 같은 반응이 나온다.

그런데 또 막상 이걸 적용하려고 하면 굉장히 힘들다.

그렇지만 세상은 쳐맞는 기회를 받기조차 어려운 것이 현실이다.

그러나 흙 속에도 한줄기 빛이 있으니, 프로젝트나 나만의 코드를 짜면서 적용시켜보면 정말 빠르고 쉽게(?) 이해가 된다.

이번주도 프로젝트나 과제에서 배운 내용을 많이 이해해보도록 해야겠다.

개괄

이제 겨우 포트 3000과 8080은 연결했다.

그런데 내가 만들어야 하는것은 FE에서 준 query를 가지고 youtube에 get 방식으로 요청을 보내서 받은 response를 파싱해서 다시 FE로 보내주어야 프론트에서 그것을 렌더링할 수 있고, 그 기능을 실제로 구현해야 한다!

youtube api를 쓰면 쉽게 파싱할 수 있지만, 화이트카드를 계속 발급받으면서 Auth를 하는데 번거로움이 있어 그냥 대가리 박고 파싱하기로 했다.

 

문제 발생

근데 이거 어케함?

8080에 요청이 들어올때 response를 쥐어주는 법, request로 파싱하는 법은 알았다.

근데 내가 클라이언트 입장에서 다른 서버에 요청을 보내고 받아오는 것은 한번도 해보지 않은 기술이라 벌벌 헤멨다.

어찌저찌 유튜브에서 받아온 html(유튜브는 SSR 방식을 이용하기 때문에, 쉽게 말하면 빈 템플릿을 보내고 그 안에 이런저런 요청받은 정보들을 가져와서 렌더링한다). 은 또 어떻게 파싱할꼬...

 

시도해본 것들

일단 처음으로 youtube에 get 메서드로 통신하기 위해서는 여러 방법이 있었다.

  • HttpUrlConnection 사용하기 -> 너무 옛날 기술이라 요즘엔 잘 사용하지 않는다고 함
  • RestTemplate -> Spring 3 이후에 나온 기술, 현재는 또 Depricated당했다.
  • WebClient -> Spring 5 이후에 나온 기술

그중에 RestTemplate를 사용해보기로 했다.

UriComponentsBuilder를 이용해 Uri에 쿼리를 담아 보냈는데,

텅 빈 ResponseBody만 온다..

다음 코드의 문제점은 뭘까?

8080포트에서는 200번대로 다 떴는데, status code를 로그로 찍어보니 300번대가 떴다.

왜 301이 떴지? 하면서 온갖 생각을 다 하다가 하나를 생각해냈다.

HTTPS

HTTPS 프록시 기술을 사용하기 때문에 리다이렉팅을 하는 과정에서 불러오지 못했던 것이었다.

이걸가지고 3~4시간을 헤메다니...

아무튼 성공했고, 다음과 같은 responseBody를 받았다.

전체는 이거의 한 6배정도 되는 것 같다.

이거를~ 어떻게~ 파싱할까요~?

이 긴 String 사이에 JSON이 숨어있다. 그 숨겨진 JSON을 꺼내서 우리가 원하는 타이틀로 바꿀것이다.

jsoup은 html parser라 html이 변동되면 사용할 수 없기 때문에

youtube api를 쓰지 않으려면 일일히 파싱을 해 주어야 한다. (CSR은 깡통 html만 주기 때문에 jsoup을 쓸 수 없다.)

파싱은 JSONParser를 이용해서 파싱하려고 했다.Json.simple 라이브러리를 이용해서 바꾸어 주려고 하니

이 안에 파싱을 할 때 JSONObject로 파싱되는지, JSONArray로 파싱되는지에 따라 계속 확인해주어야 하고, 계속 형변환을 해 주었다.

JSONParser를 쓰는 레퍼런스들은 많아서 찾아볼만했지만 보통은

[{곤충1 : 잠자리}, {곤충2 : 매미}] 등의 단순화된 스트링에서만 파싱을 했고,

{asdfasdfdsfa:{asdfsdafsdfa{asdfdsaffdsafds:gdfadsaffdsafsadfdsa:sdaffsadfsadfdas:···}···}···}등의 구조를 알아보기 힘든 긴 JSON을 파싱하는 레퍼런스들은 없었기에, 일일히 해 주어야 했는데,

이 문장을 1억줄 쓰려니 생각만 해도 아찔했다.

바로 ObjectMapper의 등장이다.

ObjectMapper에서 readTree로 데이터를 읽으면 JsonNode로 반환한다. 더욱이나 좋은것은, get 메서드 안에 index와 fieldname을 둘 다 혼용해서 쓸 수 있기에 형변환을 안해주어도 된다는 점이 매우매우 마음에 들었다.

 

알게 된 것

머리를 박으면서 코딩을 하다보니 알게된것들이 있었다.

1. BE api의 flow

MusicParsercontroller에서 MusicParserService를 const injection을 통해서 주입하고 GetMapping으로 /search에서 FE에서 날려준 request를 가지고 MusicparserService.method(request)한다는 것

2. RestTemplate의 흐름

resttemplate 구현 => getForObject로 json형식의 string값을 받고(getForEntity써서 entity로 결과 반환받아도된다.)
=> 그다음에 그걸 service단의 muisicparser로 가져가서 파싱하고 =>그다음에 프론트에 정보 줌

 

그럼 이제 파싱을 해서 프론트로 던져주는 작업을 해 보자.

개괄

알고리즘 주차도 정말 힘들지만, 개인적으로는 정말 존경하는 김영한 선생님이 말씀하셨던 정말 잘하는 주니어의 특징인 '야생형'이 그리워졌다. 마침 내가 정말 존경하기도 하고 가장 번뜩이는 아이디어를 가졌다고 생각하는 프론트엔드 친구가 프로젝트를 추천했고, 같이 하게 되었다.

거창한 프로젝트는 아니고 2명이서 하기에 사실 git flow나 개발 프로세스를 익히기엔 턱없이 부족하지만, 실전 프로젝트 대비를 하는 입장에서는 정말 좋다고 생각했다.

늘 어디서 받은 js/html로 렌더링을 하며 혼자 개발 공부를 해왔지만, 이번에 처음으로 다른 언어와 같이 소통하고 정보를 주고받는다는것이 나를 두근두근하게 만들었다.

 

문제 발생

혼자하는 개발은 나혼자 정보를 주고받지만, '협업'은 다른 '사람'과 정보를 주고받을 수 있어야 한다.

FE는 3000 포트를 가져가고, 스프링부트는 8080부트를 가져간다.

그 사이에서 정보를 주고받아야 하는데, 처음부터 갑자기 너무 막막해졌다.

"프로젝트 생성은 했지만, 어떻게 해야 정보를 주고받을 수 있을까?"

혼자 인강을 보고 혼자 개발을 하면 8080포트만 사용하면 되는데, 3000 포트와는 어떻게 통신하는지를 몰랐다.

기능 개발에 앞서 리액트와 스프링부트 연동 과정부터 진행해야 했는데, 해본적도 없고 지금은 협업 주차도 아니니

BDD(Blog-Driven-Development)를 해야 했다. 그러나 BDD에는 큰 문제가 있었는데, 각 블로그마다 문제는 같아도 해결하는 방식이 천차만별이었다.

 

시도해본 것들 ( 백엔드 입장) : CORS 오류가 뜰 것을 대비하고 작성함

1. 먼저 Controller를 만든다

RequestMapping, GetMapping은 실험하기 위해 일부러 다르게 설정해 보았다.

2. setProxy.js파일을 생성해본다

Origin 구성요소가 하나라도 다르면 CORS 에러가 발생한다고 해서 미들웨어 설치도 해 보았다.

3.app.tsx에 axios과 관련된 설정 import해주고, 

4. WebConfig 설정파일 만들기

이렇게 했더니 3000 포트에서 8080 포트의 내용을 받아올 수 있었다.

3000포트에서 "Hello MeoHyun"을 받아왔을 때의 그 쾌감은 처음에 Hello World를 쳤을때보다 쾌감이 30배 이상이었다.

그런데 문제가 생겼다.

나는 되는데 프론트단에서는 접속이 불가능했다.

찾아보니 이 에러는 IP주소가 비공개로 설정되어있을 경우 값을 할당받지 못해서 생기는 에러라고 한다.

생각해보니 나는 노트북에서 wi-fi 환경으로, FE는 랜으로 접속해서 오류가 터지는 것이었다.

그래서 root directory에 .env 파일을 생성해서 

DANGEROUSLY_DISABLE_HOST_CHECK=true

이 한줄만 넣어주니 말끔히 완료되었다.

나중에 다른 백엔드에 물어보니, package.json에 프록시 코드 하나만 추가해주면 된다고 하였다.

 

협업이라는게 그냥 같이 코드만 만지고 하는건 줄로만 알았는데, 생각보다 복잡했다. 그리고 배울게 너무나 많아졌다.

개발이 정말 재미있다. 

'웹개발' 카테고리의 다른 글

TIL - 스프링 프레임워크에 관한 간단 정리  (0) 2023.02.12

개괄

주특기 세션으로 들어가는 길목의, 어쩌면 가장 중요하면서도 가장 핵심적인 주

자바를 배우는 주간이었다.

개인적으로 Spring에서는 자바의 모든 문법들을 자유자재로 구사할수 있을 정도는 되어야 한다고 생각하는데,

그래서 자바에 대해서, 그리고 스프링을 하는 이유인 '객체지향'을 위해 얼마만큼 다가가느냐가 핵심이 되겠다.

 

목표

객체지향 프로그래밍이란 무엇인지 깨닫고 JVM은 왜 존재하는지, 그리고 JVM의 동작 원리에 대해 이해하고 CS지식은 왜 쌓아야 하는지를 알아야 한다.

 

배운 것 - TIL SERIES(LINK)

oop의 핵심요약

https://choincnp.tistory.com/28

 

TIL - OOP 요약

OOP 핵심정리 캡슐화 상속 추상화 다형성 OOP의 장점 유지보수 Easy 재사용성 ⬆️ 중복제거 Easy OOP 작성 순서 클래스 생성 (설계도 작성) 인스턴스 생성 (제품 생성) 인스턴스 사용 (제품 사용) 클래

choincnp.tistory.com

 

JVM이란 무엇인가요

https://choincnp.tistory.com/22

 

TIL - THE JAVA(1), JVM

개괄 IT 인프라 구조라는 책을 읽다가 문득 웹 서버와 AP 서버에 대한 이야기가 나왔다. 웹 서버의 OS 안에는 '프로세스'라는, 독립된 메모리 공간을 가진 것이 있다. AP서버에도 '스레드'라는 프로

choincnp.tistory.com

 

목표 달성 여부

객체 지향적 언어란 말 그대로 '지향'일 뿐이다. 객체 지향적 언어에 정답은 없다고 생각한다.

객체 지향에 다가가고 있어도, '이것이 객체 지향이다'는 말은 섣불리 할 수가 없었다.

SOLID 원칙을 배우면서 객체를 어떻게 구성해야 하는지에 대해서도 조금은 알 수 있었지만,

내가 앞으로 짜는 코드에 대해서 어떻게 객체지향적으로 짜야하는지는 아직 너무 이르기도 하고 디자인 패턴들을 많이 배워야 할 것 같다. JVM도 마찬가지다. CS지식이 전무한 내게 JVM의 시스템 구조를 조금 본 것은

혀에 잠깐 한 방울 닿은 정도로만 다가왔다.

그래서 이번주는 솔직히 점수를 많이 줄 수 없다고 생각한다.

조금 더 과장을 보태면 0점을 주고 싶다.

 

느낀 점

아는 것을 다시 공부하기는 굉장히 힘들다. 자바의 정석을 몇번이나 봐 온 나로써는 솔직히 조금 지루했다.

문제는 지루하면 안 된다는 것에 있다. 볼때마다 새로운 지식이 나오는데도 불구하고 무엇에 우선순위를 두어야 하는지에 대해 계속 뺑뺑 돌았다. 알고리즘 공부하다가 자바보다가 새로운 지식 보다가 하니 결국 남은게 없었다.

아는 형에게 정말 좋은 제안이 왔다. 스프링 개발을 부로, swift를 주로 하는 곳인데, 과장님이 나를 좋게 보신다고 이력서를 한 번 내보라고 했었는데, 그것에 대해 정말 고민했다.

그리고 나서 며칠 뒤 밤에, 같이 개발자를 목표로 했던 동료에게도 전화가 왔다. 면접을 봤었는데 스프링은 왜 쓰는지, 또 다른 개념에 대해선 무엇을 알고 있는지를 물어봤다고 했다. 곰곰히 생각해 봤는데 내가 거기에 대해서 답변할 수 있는게 너무 적었다. 그래서 부끄러워서 아는 형에게 조금 더 공부해야 할 것 같다고 연락을 남겼다.

이 상태로 취업을 했다간 나보다 나를 소개시켜준 형에게 부끄러워서 회사를 다니지 못할것 같았다. 취업시켜준다는 말은 안했지만... 정말 많은 것을 보고 듣고 알고 가야겠다고 생각했다.

 

다음 주 목표

하나에 몰두하지 않고 여러개를 돌린 시도 자체는 좋으나 결과가 너무 참담했다. 조금 더 개념에 치중해서 공부하고, 스프링 주간에 가면 할 게 많으니 빠트리지 말고 모조리 챙기고 가도록 하자. 

 

+ Recent posts