Skip to content

[Feat/#3] Github API 데이터 처리를 위한 프로토타입 Demo 서버 구현#4

Open
SHKim55 wants to merge 9 commits intomainfrom
feat/#3
Open

[Feat/#3] Github API 데이터 처리를 위한 프로토타입 Demo 서버 구현#4
SHKim55 wants to merge 9 commits intomainfrom
feat/#3

Conversation

@SHKim55
Copy link
Contributor

@SHKim55 SHKim55 commented Mar 18, 2024

구현사항

  • 서버의 동작 결과를 보여줄 test page 생성
  • Service 내 테스트 데이터 기반 CRUD 일부 기능 구현 (C, R)
  • GitHub API로 데이터 요청 (commit 데이터)
  • 요청 받은 API 데이터 페이지에 출력

설계 중점사항

  • Spring LayredArchitecture 적용을 위해 구현 코드를 Controller/Service/Domain/Others(Exception 등)로 구분
    (참고: https://hudi.blog/layered-architecture/)
  • 깃허브 API 관련 Controller 로직은 MainController에서 분리하여 작성

기타 사항 및 향후 발전 방향성

  • 현재 빠른 기술 시현만을 위해 아키텍쳐 및 객체지향적 설계, 코드 컨벤션 등을 일체 고려하지 않은 raw code임을 인지하고 리뷰해주셨으면 좋겠습니다.
  • 코드를 실행하면 단순히 api에서 가져온 데이터를 String을 변환하여 출력하는 것 밖에 없기 때문에 추후에 정제 작업이 들어갈 예정입니다.
  • 페이지에 표시할 내용, API에서 가져올 데이터 범위, 항목별 가중치를 통한 유저 경험치 설정 등 좀 더 세부적인 기획안이 나온 이후에 추가 구현이 가능할 것으로 보입니다.
  • 전체적인 Code Convention, Documentation Convention (PR, Issue, commit log 작성 포함)과 같은 협업 세부사항 또한 다음 회의에서 정해져야 될 것 같습니다.

@dmstjdhdh
Copy link
Contributor

의문점 몇가지 여쭤봐도 괜찮을까요?

  1. Spring LayredArchitecture는 Spring을 사용하는 소프트웨어에서 효율적인 개발과 유지보수를 위한 방식 중 하나일까요? 프로젝트의 요구사항이나 팀 선호도를 고려하였을 때, 상세한 정보가 주어져있지 않았을 때, 기본적으로 잡고가기 좋은 아키텍쳐이기 때문에 선택한 것 같아 맞는지 확인해보고 싶었습니다!
  2. raw code는 코드 컨벤션, 객체지향적 설계를 고려하였을 때 수정되는 코드일까요? 삭제, 혹은 dummy 코드일 가능성도 있는 코드인걸까요?
  3. 코드 컨벤션은 누가 작성해야 효율적일까요?

@SHKim55
Copy link
Contributor Author

SHKim55 commented Mar 18, 2024

  1. 해당 아키텍쳐의 경우 현재 Spring 개발에서 주로 사용되는 아키텍쳐 중 하나로, '관심사의 분리'(Separation of Concerns)라는 설계적 목표에 최대한 다가갈 수 있는 아키텍쳐입니다. Controller와 Service, Domain은 프로그램 내에서 수행하는 역할이 극명하게 나누어지며, Controller와 Domain은 서로 절대로 상호의존 할 수 없다는 특징을 가집니다. 프로젝트나 Spring에 대한 제반 지식이 없어도 디렉토리에 따라 클래스 별 기능들을 명확하게 구분할 수 있기에 현재로서는 해당 아키텍쳐를 채택하였습니다. 향후 MVC나 다른 아키텍쳐로의 전환도 고려할 예정입니다.

  2. 기존에 사용했던 코드를 최대한 재사용하는 것을 목표로 하기 때문에 Dummy 코드로 처리되어 아얘 없애는 일은 없을 것으로 보입니다.

  3. 실제 개발진들이 작성하는 것이 효율적이지만 모든 구성원들이 개발에 다 참여하는 저희 프로젝트의 특성 상 정기회의 시간에 같이 정하는 것이 좋아보입니다.

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RestController는 어떤 역할을 하는 코드인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring에게 해당 클래스가 Rest API를 처리하는 Controller의 역할을 한다고 알려주는 Annotation 입니다. 이걸 붙이면 자동으로 Spring Bean으로 등록됩니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Bean이 뭔가요? 재사용 가능한 소프트웨어 컴포넌트라고 나오는데, 해당 컨트롤러가 Spring Bean으로 등록이되면 어떤 이점이 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bean은 스프링에서 직접 관리해주는 객체라고 보시면 됩니다. 자동 의존관계 주입(Dependency Injection, DI)이라는 스프링의 핵심 기능을 사용하기 위해 등록하여야 하며, 등록이 되게 되면 한 종류의 객체는 전체 프로그램 내에서 하나만 생성되어 (Singleton) 재사용이 가능합니다. 여기서는 GithubAPIController 자체가 하나의 Bean이므로 프로그램 내에 다른 클래스에서 새로운 GithubAPIController 필드를 선언해도 처음에 생성된 것과 똑같은 Controller 객체가 필드에 주입되게 됩니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자동 의존관계 주입이 뭔지 혹시 설명해주실 수 있을까요? 해당 용어를 제외하고는, 어떤 역할을 하는지 이해는 갑니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제대로 설명하기엔 어려운 개념이라 간단하게만 설명하자면, A클래스에서 B 타입의 필드를 생성할 때 선언을 한 뒤 new 키워드를 이용하여 B 객체를 할당을 받게 되면 A는 B에 의존한다고 표현합니다. A클래스는 B 객체 필드 없이는 동작하지 못하기 때문입니다. 여기서 자동으로 의존관계를 주입한다는 것은 객체의 생성자를 이용하여 스프링이 객체 내 필드를 자동으로 생성시켜준다는 것을 의미합니다. Int a = new Int()와 같은 형식으로 직접 할당해 줄 필요가 없습니다.


@RestController
public class GithubAPIController {
private final GithubAPIService githubAPIService = new GithubAPIService();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 final 키워드를 쓴 이유가 뭔지 알려주실 수 있나요? 또한, GithubAPIService는 어떤 객체인지 궁금합니다!

Copy link
Contributor Author

@SHKim55 SHKim55 Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이름에서 알 수 있듯이 Github API를 활용할 수 있게 만들어주는 서비스를 담당하는 객체 필드를 의미합니다. 실제로 API에서 데이터를 불러오는 로직은 Controller가 아닌 Service에서 처리합니다. 해당 객체는 이후에 다른 객체로 변환될 일 없이 쭉 불변이므로 final access modifier를 사용했습니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 이해했습니다! 객체 필드에 대한 로직은 모두 Service에서 처리하기 때문이군요! 좋은 설명 감사합니다!

public class GithubAPIController {
private final GithubAPIService githubAPIService = new GithubAPIService();

@GetMapping("/github/{name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetMapping은 HTTP GET 요청을 처리하기 위한 어노테이션인가요? 어노테이션의 괄호 안에 "github/{name}"이 들어가있는데, 이 코드가 들어가 있는 이유가 뭔지 알려주실 수 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

괄호 안에 들어가있는 URL에서 GET 요청을 처리하는 코드입니다. {name}은 github/ 뒤에 위치한 URL 내 값을 name 변수로 받아서 활용하겠다는 의미입니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어노테이션에서 이런식으로 api 요청이 가능하군요... 감사합니다!

@GetMapping("/github/{name}")
public String printGithubProfile(@PathVariable("name") String name) {
System.out.println(name);
String repo = "spring-roomescape-playground";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수 이름이 repo인 이유는, user의 profile의 name을 출력함과 동시에, 특정 repository 이름을 출력하기 위함인가요?

뭔가 헷갈리네요. 함수명은 printGithubProfile인데, printCommitInfo(name, repo)을 return하고있어요. 함수명에 맞춰서 GithubProfile이 출력되는게 맞는 기능이 아닐까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 지적입니다. 컨트롤러 분리 작업을 하다가 함수명 바꾸는 것을 까먹었네요. 저 함수의 본래 기능은 사용자의 커밋 정보를 화면에 띄워주는 것이라 return 안의 함수명이 아닌 가리키신 함수의 이름을 바꾸도록 하겠습니다.

return printCommitInfo(name, repo);
}

private String printCommitInfo(String owner, String repo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

printCommitInfo함수에서, Info가 뭔지 잘 모르겠어요. owner, repo를 매개변수로 전달하면 어떤 Info가 출력되나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 JSON으로 받아온 API 호출 결과값을 전부 반환하도록 해놨습니다. 시연 때 보여드리겠습니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오우 넵 감사합니다

private final ProfileService profileService = new ProfileService();

@GetMapping("/")
public String test() { return "test"; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String이라는 타입의 변수명을 test()로 지정할 수 있나요? 저 이 코드가 잘 이해가 안갑니다. String인데 함수형으로 오른쪽에 test() { return "test"; }이런식으로 선언되어있어요. public String test = "test";랑 뭐가 다른거죠 😢

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 이 코드는 왜 있는건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(저도 저 코드 이해하는데 참~ 오래 걸렸는데요~~)
괄호 안의 URL이 "/" 하나만으로 이루어진 것에서 알 수 있듯이, 이 코드는 URL에 다른 값들이 붙지 않아 서비스의 가장 기본이 되는 페이지(home 등)를 불러올 수 있는 코드입니다. 이 함수에서 "test"라는 String 값을 반환하게 되면 Spring 자체에서 "test"라는 이름의 HTML 파일을 프로젝트 내에서 찾아 서버로 띄워주고 GET 요청을 마무리합니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이야 그러한 기능이라면, 서비스 단계에서는 test라는 명칭 말고, 다른 명칭으로 많이 구현이 될 것 같은데 제 추측이 맞을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실제 서비스를 해보지 않아서 잘은 모르겠지만, 가장 기본이자 메인이 되는 페이지이기 때문에 home, main, 서비스 이름 등으로 명칭을 정하는 것으로 알고 있습니다.

public Profile(final Long id, final String name, final String time, final String date) {
this.id = id;
this.name = name;
this.recentTime = LocalTime.parse(time);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalTime.parse(time)을 하면, recentTime은 어떻게 초기화 되나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HH:mm:ss 형식으로 들어온 String 데이터를 시간 처리 객체인 LocalTime 객체로 변환합니다.


@Service
public class GithubAPIService {
private final String GITHUB_API_URL = "https://api.github.com";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기본 baseURL이 되는 코드인가보네요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다!

public class GithubAPIService {
private final String GITHUB_API_URL = "https://api.github.com";

private final RestTemplate restTemplate = new RestTemplate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestTemplate()는 어떤 객체인지 설명해주실 수 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST API에서 보내주는 값을 저장하기 위해 사용해야되는 템플릿입니다. 하단의 코드를 보시면 restTemplate.queryForObject() 메서드로 데이터를 Object 형태로 받아오는 것을 알 수 있습니다.


private final RestTemplate restTemplate = new RestTemplate();

public GithubAPIService() { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이후에는 어떤 기능이 해당 함수에 추가될 가능성이 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 생성자라 특정한 기능을 하는 함수가 추가되지는 않겠지만, 스프링이 RestTemplate 필드에 의존성을 추가하는 코드가 들어갈 예정입니다.


public GithubAPIService() { }

public String getCommits(String owner, String repo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit을 가져오는 함수인걸까요? 뭔가 getUrlString이런 함수명은 url의 string값을 가져온다... 이런식으로 이해가 되는데 커밋을 가져온다는건 뭔지 모르겠어서 어떤 기능을 하는지 잘 이해가 가지 않습니다! 설명해주실 수 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(함수명은 GPT가 짰어요...)
REST API를 통해 owner가 가지고 있는 특정 repo의 commit 정보를 모두 가져옵니다. commit 정보에는 일시, 사용자, 커밋 메시지 등 다양한 내용이 포함되어 있습니다. 실제 형식은 시연 때 보여드리겠습니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다~

@dmstjdhdh
Copy link
Contributor

핑프짓 한번 하겠습니다... domain, controller, service가 각각 어떤 단위로 나눠지는거죠? 각각 이름마다 뭘 하는 단위인지 이해가 먼저 되면 좋을 것 같다는 생각이 있습니다!

@SHKim55
Copy link
Contributor Author

SHKim55 commented Mar 18, 2024

Controller(Web): 사용자와 직접적으로 상호작용하는 로직이 위치한 계층 (입출력, GUI 등)
Service: 핵심 비즈니스 로직, Core 로직을 담고 있는 계층
Domain(Repository): Service의 기반이 되는 데이터들이 위치한 계층 (도메인 객체, 엔티티 등)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants