[인프런 워밍업 클럽 1기] BE 1주차 발자국

[인프런 워밍업 클럽 1기] BE 1주차 발자국

인프런 워밍업 클럽 1주차 발자국

서론

IT 취준생 톡방에서 우연히 워밍업 클럽에 대해 정보를 얻을 수 있었고, 내가 들을만한 좋은 강의가 있을까 찾아보니 마침 최근에 포트폴리오 때매 관심 있었던 스프링으로 직접 서버를 배포하는 방법이 강의 주제로 있어서 신청하게 되었다.

무엇보다 혼자 강의를 듣는게 아닌 디스코드 커뮤니티에서 서로 물어보며 강의를 같이 듣는다는 것이 흥미 있었고 모든 과정을 수강 완료하면 인프런 포인트로 5만이나 환급된다는 것이 마음에 들었다.

앞으로 강의와 스터디를 하면서 배운 내용을 잊지 않도록 주마다 개인 블로그와 인프런 블로그에 정리하려고 한다.

섹션 0 : 소개와 준비, 수업자료

강의 소개 영상

  • 강의 제목 : [All-In-One] 자바와 스프링 부트로 생애 최초 서버 만들기 누구나 쉽게 개발부터 배포까지!!

  • 강의 소개 중 서버 개발자, 백엔드 개발자가 되기 위해선 다방면에서 부족함이 없어야 한다는 점에 공감했다.

  • 강의에서 다루는 내용은 다음과 같다고 한다. Java를 기반으로 Linux, Spring boot, mysql, gradle, JPA, Github, aws 등...

  • 그 외 서버 개발에 필요한 전반적인 이론 / 개념 설명도 같이 진행하는 것 같다.

강의 자료(완성된 코드)

  • 강의 자료는 간단하게 강의에서 사용하는 초기 Project 파일과 PPT, PDF 파일로 구성되어 있었다.

환경 설정하기

  • 강의를 시작하기에 앞서 Java, IntelliJ, PostMan, MySQL, git 설치를 진행했다.

  • 다른 환경은 이전에 경험해본적이 있지만, PostMan 은 처음 경험해보는 것이라 이렇게 좋은 도구도 있구나 생각했다.

스프링 프로젝트를 시작하는 첫 번째 방법

  • 프로젝트의 초기 셋팅 과정을 진행했다.

  • 이전에 인프런에서 강의자료를 다운로드 받을 수 있었고 자료 내부에 있는 초기 Project 파일을 열어 실행시켜보니 터미널에 스프링 부트가 실행되는 것을 확인할 수 있었다.

image

섹션 1 : 생애 최초 API 만들기

Java를 공부하기 전에 알아두면 좋을 것들 #1 (JVM, JDK)

  • 강의를 시작하기 전에 JVM과 JDK에 대해 간단하게 확인했다.

  • 정리 1.

    • JAVA라는 언어를 컴퓨터가 알아먹기 까지 과정

    • 컴파일 : 인간이 이해하기 쉬운 언어를 기게어로 번역하는 과정

    • 컴파일러 : 컴파일을 하는 프로그램

    • 바이트 코드 : 0과 1로 이루어진 코드, 컴퓨터가 이해할 수 있다.

  • 정리 2.

    • OS마다 0과 1의 조합이 다르다.

    • 원래, OS 마다 다른 컴파일러가 필요하는게 맞다.

    • 그러나 JAVA는 JVM이 0과 1을 OS에 맞게 번역해준다.

    • JVM은 JAVA 외에 다른 언어에서도 사용된다.

  • 정리 3.

    • JVM < JRE < JDK 순서로 포함관계

    • JDK를 설치하면 JRE, JVM도 같이 설치됨, 즉 JAVA의 버전 = JDK의 버전

    • JVM : 자바 가상 머신 (Java Virtual Machine) 의 약어

      • OS 별로 존재한다.

      • 바이너리 코드를 읽고 검증하고 실행한다.

    • JRE : 자바 실행 환경 (Java Runtime Environment) 의 약어

      • JRE = JVM + 자바 프로그램 실행에 필요한 라이브러리 파일 등

      • JVM의 실행 환경을 구현한다.

    • JDK : 자바 개발 도구 (Java Development Kit) 의 약어

      • JDK = JRE + 개발을 위한 도구

      • 컴파일러, 디버그 도구 등이 포함된다.

  • 정리 4.

    • JDK는 버전이 존재하고, 각 버전별로 새로운 기능이 추가되거나 기존 기능이 사라짐.

    • JDK는 종류가 존재하고, 기능 자체는 모두 동일하지만 성능과 비용에 약간의 차이가 존재함.

    • LTS(Long Time Support) 버전 : 오래 써도 좋도록 지원하는 버전

    • JDK의 종류

      • Oracle JDK : 오라클에서 만든 JDK, 개인 무료, 기업 유료

      • Open JDK : Oracle JDK와 비슷한 성능, 언제나 무료

Java를 공부하기 전에 알아두면 좋을 것듯 #2 (빌드, 빌드툴)

  • 앞서 알아본 내용에 더해 빌드와 빌드툴에 대해 간단하게 확인했다.

  • 정리 1.

    • 빌드 : 소스 코드 파일을 컴퓨터에서 실행할 수 있는 독립 SW 가공물로 변환시키는 과정

    • 빌드 과정 세분화

      • 소스 코드를 컴파일

      • 테스트 코드를 컴파일

      • 테스트 코드를 실행

      • 테스트 코드 리포트를 작성

      • 기타 추가 설정한 작업들을 진행

      • 패키징을 수행

      • 최종 SW 결과물을 만들어냄

    • 실행 : 내가 작성한 코드나 테스트 코드를 컴파일을 거쳐, 작동시켜 보는 것 독립적인 SW 가공물이 있을 수도 없을 수도 있다.

    • 빌드 툴 : 소스코드이 빌드 과정을 자동으로 처리 해주는 프로그램으로 외부 소스 코드 (외부 라이브러리) 등을 자동 추가, 관리해준다.

    • 현재는 주로 maven, gradle 2가지가 많이 쓰인다.

    • 빌드 툴 종류

      • Ant

        • 설정을 위해 xml 사용

        • 간단하고 사용하기 쉬움

        • 복잡한 처리를 하려 하면 빌드 스크립트가 장황해져 관리가 어려움

        • 외부 라이브러리를 관리하는 구조가 없음

      • Maven

        • 설정을 위해 xml 사용

        • 외부 라이브러리 관리 가능

        • 장황한 빌드 스크립트 문제 해결

        • 특정 경우에 xml이 복잡해짐

        • xml 자체의 한계가 존재함

      • Gradle

        • 설정을 위해 groovy 언어 사용

        • 외부 라이브러리 관리 가능

        • 유연하게 빌드 스크립트를 작성할 수 있음

        • 성능이 뛰어남

스프링 프로젝트를 시작하는 두 번째 방법

  • 스프링 프로젝트를 설정해 시작하고 실행하는 과정을 진행

  • start.spring.io 사이트에서 스프링 프로젝트의 초기 설정을 비교적 간단하게 진행할 수 있다.

image

  • 또한 2024년 기준 11버전 선택이 존재하지 않아 자료로 주어진 초기 프로젝트 파일로 진행하려고 한다.

  • 라이브러리 : 프로그래밍을 개발할 때 미리 만들어져 있는 기능을 가져다 사용하는 것.

  • 프레임워크 : 프로그래밍을 개발할 때 미리 만들어져 있는 구조에 코드를 가져다 끼워 넣는 것.

@SpringBootApplication과 서버

  • 어노테이션 : 자바에서 어노테이션(Annotation)이란 소스 코드에 메타데이터를 추가하는 방법을 제공하는 기능이다.

     

  • 서버 : 어떠한 기능을 제공하는 프로그램, 그 프로그램을 실행시키고 있는 컴퓨터

  • 서버가 정해진 기능을 동작하려면 서버에게 요청을 해야한다.

네트워크란 무엇인가?

  • 컴퓨터별 고유주소 IP와 인터넷을 통해 데이터를 주고 받을 수 있다.

  • 데이터를 받는 컴퓨터는 IP 244.66.51.9, port:3000과 같은 주소로 받음.

  • IP 주소를 외우기 어려워 외우기 쉽게 이름으로 변경한 DNS (Domain Name System) 이 나타남.

HTTP와 API란 무엇인가?

  • HTTP : HyperText Transfer Protocol의 약어로서 데이터를 주고 받는 표준.

  • HTTP 메소드의 종류

    • GET : 데이터 제공 요청, 쿼리 사용

    • POST : 데이터 저장 요청, 바디 사용

    • PUT : 데이터 수정 요청, 바디 사용

    • DELETE : 데이터 삭제 요청, 쿼리 사용

  • 요청 메소드에 따라 쿼리를 사용할지 바디를 사용할지 다르다.

  • API : Application Programming Interface의 약어로서 정해진 약속을 하여, 특정 기능을 수행하는 것.

  • HTTP 요청 문법

    • 첫째줄 (메소드 패스 쿼리)

    • 헤더 (여러 줄 가능)

    • 한줄 공백

    • 바디 (여러 줄 가능)

  • URL : Uniform Resource Locator의 약어로서 사용하고 있는 프로토콜 + 구분기호 + 도메인 이름:포트 + 자원 경로 + 구분기호 + 쿼리 로 구성되어 있다.

  • HTTP 응답 문법

    • 첫째줄 (상태코드)

    • 헤더 (여러 줄 가능)

    • 한줄 공백

    • 바디 (여러 줄 가능)

  • 정리 1.

    • 웹을 통한 컴퓨터 간의 통신은 HTTP라는 표준화된 방식으로 동작함.

    • HTTP 요청은 HTTP 메소드와 Path 등으로 이루어짐

    • 요청에서 데이터를 전달하기 위한 2가지 방법은 쿼리와 바디

    • HTTP 응답은 상태 코드가 핵심이다.

    • 클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하며 이때 정해진 규칙을 API라고 함.

GET API 개발하고 테스트하기

  • 덧셈 API 구현

    • HTTP Method : GET

    • HTTP Path : /add

    • 쿼리(key, value) : int number1 / int number2

    • API 반환 결과 : 숫자 - 두 숫자의 덧셈 결과

  • @RestController : 주어진 Class를 Controller로 등록

  • @RequestParam : 주어지는 쿼리를 함수 파라미터에 넣음.

POST API 개발하고 테스트하기

  • 곱셈 API 구현

    • HTTP Method : POST

    • HTTP Path : /multiply

    • HTTP Body(Json) : { "number1":숫자, "number2":숫자 }

    • API 반환 결과 : 숫자(곱셈 결과)

  • @RequestBody : HTTP Body로 들어오는 JSON을 CalculatorMultiplyRequest로 바꿈.

유저 생성 API 개발

  • 사용자 등록 API 구현

    • HTTP Method : POST

    • HTTP Path : /user

    • HTTP Body(Json) : { "name":String (null 금지), "age":Integer }

    • API 반환 결과 : 상태코드 200

유저 조회 API 개발과 테스트

  • 사용자 조회 API 구현 - HTTP Method : GET - HTTP Path : /user - 쿼리 : 없음 - API 반환 결과 : [{ "id":Long,"name":String(null 금지),"age":Integer }]

  • Controller에서 getter가 있는 객체를 반환하면 JSON이 된다.

     

Section1 정리.

  • 스프링 프로젝트를 시작하는 방법과 실행하는 방법

  • 네트워크, IP, 도메인, 포트, HTTP 요청과 응답 구조, 클라이언트 - 서버 구조, API와 같은 기반 지식

  • Spring Boot를 이용해 GET API와 POST API를 만드는 방법

섹션 2. 생에 최초 Database 조작하기

Database와 MySQL

  • RDB : Relational Database의 약어로서 데이터를 표처럼 구조화 시켜 저장하는 도구

  • SQL : Structured Query Language의 약어로서 표처럼 구조화된 데이터를 조회하는 언어

MySQL에서 테이블 만들기

  • 폴더 = 데이터베이스

  • 엑셀 파일 = 테이블

  • 엑셀 파일의 헤더 = 테이블의 필드 정의

  • 데이터베이스를 만들고 이동해서, 테이블을 만든다. 이때 테이블 이름, 타입, 부가조건을 지정한다.

  • 데이터베이스 생성 : create database [데이터베이스 이름];

  • 테이터베이스로 이동 : use [데이터베이스 이름];

  • 테이블 생성 : create table [테이블 이름] ([필드1 이름] [타입] [부가조건], [필드2 이름] [타입] [부가조건]... primary key([필드이름]));

  • 테이블 제거 : drop table [테이블 이름];

  • DDL(Data Definition Language) : 데이터 정의어

테이블의 데이터를 조작하기

  • 데이터 넣기 = 생성, Create

  • 데이터 조회 = 읽기, Retrieve or Read

  • 데이터 수정 = 업데이트, Update

  • 데이터 삭제 = 제거, Delete

  • 데이터 넣기 : insert into [테이블 이름] (필드1 이름, 필드2 이름,...) values(값1, 값2,...) / auto_increment : 값이 자동 증가

  • 데이터 조회 : select * from [테이블 이름] where [조건];

  • 데이터 수정 : update [테이블 이름] set 필드1 이름 = 값, 필드2 이름 = 값, ... where [조건];

  • 데이터 삭제 : delete from [테이블 이름] where [조건]; / 조건을 붙이지 않을시 모든 데이터가 삭제된다.

  • DML(Data Manipulation Language) : 데이터 조작어

Spring에서 Database 사용하기

  • Spring 서버가 MySQL DB에 접근하게 하기.

  • application.yml 설정

spring:
datasource:
  url: "jdbc:mysql://localhost/library" // jdbc:mysql:// -jdbc 로 mysql에 접근
  username: "[계정 명]"
  password: "[계정 비밀번호]"
  driver-class-name: com.mysql.cj.jdbc.Driver // 데이터베이스에 접근하기 위한 프로그램
  • POST API 변경

private final JdbcTemplate jdbcTemplate; // jdbcTemplate를 이용해 SQL을 날리는 형태로 변경.

public UserController(JdbcTemplate jdbcTemplate) {
  this.jdbcTemplate = jdbcTemplate; // 생성자를 만들어 jdbcTemplate을 파라미터로 넣으면, 자동으로 들어옴.
}
@PostMapping("/user") // POST /user
public void saveUser(@RequestBody UserCreatedRequest request) {
  String sql = "INSERT INTO user (name, age) VALUES (?, ?)"; // SQL을 만들어 문자열 변수로 저장함.
  jdbcTemplate.update(sql, request.getName(), request.getAge()); // INSERT, UPDATE, DELETE 쿼리에 사용되는 update 함수 정의
}
// 첫 파라미터로 sql을 받고, ?를 대산할 값을 차례로 넣으면 됨.
  • GET API 변경

@GetMapping("/user")
  public List<UserResponse> getUsers() {
      String sql = "SELECT * FROM user";
      return jdbcTemplate.query(sql, new RowMapper<UserResponse>() {
          @Override
          public UserResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
              long id = rs.getLong("id");
              String name = rs.getString("name");
              int age = rs.getInt("age");
              return new UserResponse(id, name, age); // UserResponse에 생성자 추가
          }
      });
  }
// jdbcTemplate.query(sql, RowMapper 구현 익명 클래스)

유저 업데이트 API, 삭제 API 개발과 테스트

  • 사용자 수정 API 구현

     

    • HTTP Method : PUT

    • HTTP Path : /user

    • HTTP Body(Json) : { "id":Long, "name":String }

    • API 반환 결과 : 상태코드 200

     

  • 사용자 삭제 API 구현

    • HTTP Method : DELETE

    • HTTP Path : /user

    • 쿼리(key, value) : 문자열 name

    • API 반환 결과 : 상태코드 200

     

  • 만약 업데이트나 삭제를 할때 존재하지 않는 유저가 있다면, 예외를 던져서 처리해줘야 할 것이다.

유저 업데이트 API, 삭제 API 예외 처리 하기

  • 없는 유저를 업데이트 하거나 삭제하려 하면, API에서 예외를 던져서 처리할 수 있다.

  • 예외를 처리하는 API는 실제 데이터에서 데이터 존재 여부를 확인해서 처리한다.

String readSql = "SELECT * FROM user WHERE id = ?"; // id를 기준으로 유저가 존재하는지 확인
boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); // SELECT의 결과가 있으면 0으로 변환되며 결론적으로 id가 있으면 0이 있는 리스트 반환, id가 없으면 아무겂도 없는 리스트 반환.
if (isUserNotExist) { // 유저가 존재하지 않으면 예외를 던진다.
  throw new IllegalArgumentException();
}

[업데이트, 삭제 테스트 포스트맨 획인해보기]

Section2 정리

  • 디스크와 메모리 차이를 이해하고, Database의 필요성을 이해한다.

  • SQL을 이용해 MySQL Database를 조작할 수 있다.

  • 스프링 서버를 이용해 Database에 접근하고 데이터를 저장, 조회, 업데이트, 삭제할 수 있다.

  • API의 예외 상황을 알아보고 예외를 처리할 수 있다.

  • 하나의 Controller 클래스에 너무 많은 역할을 수행 중이며 해결하기 위한 방법을 알아볼 예정.

섹션 3. 역할의 분리와 스프링 컨테이너

좋은 코드(Clean Code)는 왜 중요한가?

  • 코드는 요구사항을 표현하는 언어이고 개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다.

  • 좋지 않은 코드를 작성하며 시간이 쌓이면, 시간이 지날 수록 유지보수가 힘들어 생산성이 낮아진다.

  • 코드만 보고도 의미를 파악할 수 있게끔 작성하는 것이 중요하다.

  • 함수는 최대한 작게 만들고 한 가지 일만 수행하도록 작성하는 것이 중요하다.

  • 현재 우리가 작성한 Controller의 동작 과정

    • [1] API의 진입 지점으로써 HTTP Body를 객체로 변환한다.

    • [2] 현재 유저가 있는지 없는지 확인하고 예외 처리를 진행한다.

    • [3] SQL을 사용해 실제 Database와의 통신을 담당한다.

  • Controller 를 각 과정에 맞게 3단 분리를 진행할 예정.

Controller를 3단 분리하기 - Service와 Repository

  • 현재 Controller 함수의 역할

    • API의 진입 지점으로써 HTTP Body를 객체로 변환한다. = Controller 역할

    • 현재 유저가 있는지 없는지 확인하고 예외 처리를 진행한다. = Service의 역할

    • SQL을 사용해 실제 Database와의 통신을 담당한다. = Repository의 역할

  • Repository에서 바로 jdbcTemplate을 가져올 수는 없는지 알아볼 예정.

1주차 과제

과제 1번 : https://www.inflearn.com/blogs/7658

  • 어노테이션을 사용하는 이유 (효과) 는 무엇일까?

  • 나만의 어노테이션은 어떻게 만들 수 있을까?

과제 2번 : https://www.inflearn.com/blogs/7692

  • 다양한 GET API, POST API 만들기

과제 3번 : https://www.inflearn.com/blogs/7716

  • 자바의 람다식은 왜 등장했을까?

  • 람다식과 익명 클래스는 어떤 관계가 있을까?

  • 람다식의 문법은 어떻게 될까?

1주차 후기

지난주 사전 OT 부터 시작해서 벌써, 인프런 워밍업 클럽 과정의 1주차가 마무리됬다.

이번 주차에서는 섹션 0 부터 섹션 3의 일부분의 강의를 수강할 수 있었고, 1차 중간점검 시간을 가질 수 있었다.

간단하게 각 섹션 별로 강의의 주제를 다시한번 상기하고 어떤걸 배우고 느꼈는지 확인해보려고 한다.

섹션 0의 주제 : 소개와 준비, 수업 자료

강의 소개를 들으면서 서버 개발자, 백엔드 개발자가 되기 위해선 다방면에서 부족함이 없어야 한다는 점에 예상치 못하게 공감을 받을 수 있었다.

강의 자료 또한 섹션별로 적절하게 준비되어 사전에 어떻게 공부를 진행하고 회고록을 작성해야 할지 가이드 라인이 되기도 했다.

환경 설정을 진행하면서 PostMan라는 새로운 도구에 대해 알게 되었고, 오랜만에 다루어본 MySQL도 꺼내볼 수 있었다.

섹션 1의 주제 : 생애 최초 API 만들기

자바로 프로젝트를 진행하기 전에 JVM과 JDK에 대해 알아보고, 빌드의 개념과 빌드 툴의 종류에 대한 내용을 정리하면서 다시 한번 개념적인 부분을 정립할 수 있었다.

간단하게 스프링 프로젝트를 시작하고 실행해보았고, 강의 중간에 네트워크, IP, 도메인, 포트, HTTP 요청과 응답 구조, 클라이언트 - 서버 구조, API와 같은 기반 지식에 대해 설명을 들을 수 있었다.

강의 주제에 맞게 Spring Boot를 이용해 기본적인 GET API와 POST API를 만들어보고 어떤 원리로 동작하는지 알아볼 수 있었다.

섹션 2의 주제 : 생애 최초 Database 조작하기

이전 섹션에서 수행한 과정의 문제점인 서버 재실행시 데이터가 초기화 되는 부분에 관련하여 컴퓨터의 디스크와 메모리 차이를 이해하면서 Database에 저장하는 것의 필요성을 이해할 수 있었다.

IntelliJ와 MySQL을 연결하는 방법을 배웠고, 생애 최초로 조작하는건 아니지만 SQL을 이용해 MySQL Database를 조작하는 기본적인 방법에 대해 다시 상기시킬 수 있었다.

기존에 만들어둔 스프링 서버의 API를 변경하여 Database에 접근하고 데이터를 저장, 조회, 업데이트, 삭제하는 방법에 대해 배웠고, API의 예외 상황에 대해 예외를 처리하는 과정을 학습할 수 있었다.

섹션 3의 주제 : 역할의 분리와 스프링 컨테이너

하나의 컨트롤러 함수에 수많은 기능이 몰려있으면 생기는 문제점에 대해 인식하면서, 좋은 코드가 왜 중요한지 이해할 수 있었고, 이전 섹션에서 작성한 Controller 코드를 직접 Controller-Service-Repository로 분리하면서 어떻게 하면 좋은 코드로 리팩토링 할 수 있을지 고민하는 과정을 겪었다.

1차 중간점검

커뮤니티로 사전에 코치님에게 다양한 질문을 올려 답변을 해주는 실시간 Q&A를 진행했다. 다양한 분야에서 종사하는 강의생분들이 질문을 올려주셨고, 솔직히 말하면 내가 겪은 질문이여서 공감이 되기도 했고, 내가 미래에 겪을 질문이라서 과연 어떻게 답변해주실까 궁금하기도 했다.

강의 내용에 관한 현업에서의 추가적인 질문도 있었지만, 대부분 진로나 취업의 과정에서 현업자의 시각에 대해 조언을 요청하는 질문이 많았다. Q&A 시작전에는 마땅한 질문이 생각나지 않았는데, 코치님의 답변을 듣고 몰입이 되면서 궁금한 부분이 생겨나기도 했다.

강의 막바지에 내가 물어본 질문은

"깃허브 블로그에 개인 포트폴리오 부분을 추가해서 기업 지원할때 링크를 첨부하려고 하는데 개인 블로그를 오픈해두는게 좋을까요?"

였다.

질문하게된 이유는 개발자로 지원할때 보통 PDF 파일이나 노션 링크로 포트폴리오를 등록하는 경우가 대부분인데, 나는 깃허브 블로그를 운영중이라 깃허브 블로그의 메인페이지를 포트폴리오 화면으로 바꾸어 노션 링크를 대신하려고 준비중이였다. 이 과정에서 하나의 고민거리가 있었는데 앞서 과정을 진행하면 포트폴리오 화면을 통해 개인 블로그로 이동할 수 있었는데, 과연 개인 블로그를 오픈해두는 것이. 지원하는 과정에서 메리트가 있을지 디메리트가 있을지 였다.

코치님이 조언해주신 부분은 포트폴리오 화면으로 프로젝트의 진행 과정 중 일어났던 과정을 잘 정리할 수 있으면 개인 블로그를 첨부하지 않아도 될것 같다고 하셨고, 포트폴리오 화면으로 충분히 전달하지 못한 경우에는 블로그를 사용하여 보여주는 것도 괜찮다고 하셨다. 앞으로 어떻게 포트폴리오를 준비할지 좋은 조언을 주셨다고 생각한다.

마무리

아직 1주차 이지만 전반적으로 강의에 만족하고 있으며 강의 내용도 잘 따라가고 있다고 생각한다. 중간 점검 과정을 통해 코치님에게 좋은 조언을 받기도 했고, 남은 과정도 열심히 진행해서 적어도 강의 주제인 자바-스프링-서버-배포는 확실하게 기반을 다지겠다고 마음을 가졌다.

댓글을 작성해보세요.