[정리] 우리스러움

김주형·2024년 6월 23일

뭘 이런걸 다 합니다

목록 보기
32/51
post-thumbnail


우리스러움

  • 가족모임 스케줄러

배경

  • 온가족이 각자 직장 다니느라 휴무일 조정이 어려워졌다는 문제
  • 쉬는날 함께 얼굴보기 어려워졌다는 페인포인트 발견
  • 쉽고 직관적인 스케줄링 서비스 개발로 문제 해결 시도

핵심 기능 정의

  1. 캘린더에서 휴무일을 체크한다 (빨간색으로 변경)
  2. 휴무일은 일정을 등록할 수 있다 ex) 이번주 일요일 가족 식사모임
  3. 휴무일 일정에 친구를 초대할 수 있다 ex) url 복사, 카톡 연동

내가 한것

  • 유저 문제 정의
  • 핵심 기능 정의
  • 개발 && 테스트
  • UI/UX 개선
  • AWS EC2 인스턴스 생성 및 보안 그룹 설정
  • 기타 개발 과정 중 발생하는 문제 해결

환경 설정

  • https://start.spring.io/
  • 기술 결정은 제약 내 빠른 문제 해결을 위해 다음과 같은 의존성 추가를 진행
  • Spring Web
  • Thymeleaf (템플릿 엔진)
  • Spring Data JPA
  • H2 Database

프로젝트 구조

woorinature/
|-- src/
|   |-- main/
|   |   |-- java/com/example/familyscheduler/
|   |   |   |-- WoorinatureApplication.java
|   |   |   |-- controller/
|   |   |   |   |-- CalendarController.java
|   |   |   |-- model/
|   |   |   |   |-- Event.java
|   |   |   |-- repository/
|   |   |   |   |-- EventRepository.java
|   |   |-- resources/
|   |       |-- templates/
|   |       |   |-- calendar.html
|   |       |-- application.properties
|-- pom.xml

application.properties 설정

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
  • 설정 중 Intellij CE에서 지원하지 않는 오류가 있어 학생 인증을 통해 Intellij Ulti로 업그레이드 했습니다
  • 참고

  • 모델
@Entity
public class Event {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String title;
    private LocalDate date;

    public LocalDate getDate() {
        return date;
    }

    public void setDate(LocalDate date) {
        this.date = date;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
  • 레포지토리 인터페이스
@Repository
public interface EventRepository extends JpaRepository<Event, Long> {
    List<Event> findByDate(LocalDate date);
}
  • 컨트롤러
@Controller
public class CalenderController {

    @Autowired
    private EventRepository eventRepository;

    @GetMapping("/calender")
    public String getCalender(Model model) {
        List<Event> events = eventRepository.findAll();
        model.addAttribute("events", events);
        return "calender";
    }

    @PostMapping("/add-event")
    public String addEvent(@RequestParam String title, @RequestParam String date) {
        Event event = new Event();
        event.setTitle(title);
        event.setDate(LocalDate.parse(date));
        eventRepository.save(event);
        return "redirect:/calender";
    }
}

가까운 가족들을 대상으로 최단 시간내 필요성 검증을 시도하고자 최소한의 mvc 코드를 추가하고 테스트해봤습니다!

  • 이벤트 테스트

class EventTest {

    @Test
    void 이벤트_생성_테스트() {
        // given
        Event event = new Event();
        String expect = "이번주 일요일 가족 식사 모임";

        // when
        event.setTitle("이번주 일요일 가족 식사 모임");
        event.setDate(LocalDate.of(2024, 06, 23));

        // then
        assertNotNull(event);
        assertEquals(expect, event.getTitle());
        assertEquals(LocalDate.of(2024, 06, 23), event.getDate());
    }
}
  • 이벤트저장소 테스트
@DataJpaTest
class EventRepositoryTest {

    @Autowired
    private EventRepository eventRepository;

    @Test
    public void 이벤트_저장과_조회_테스트() {

        // given
        String expect = "이번주 일요일 가족 식사 모임";
        Event event = new Event();
        event.setTitle("이번주 일요일 가족 식사 모임");

        // when
        event.setDate(LocalDate.of(2024, 06, 23));
        eventRepository.save(event);

        // then
        List<Event> events = eventRepository.findByDate(LocalDate.of(2024, 06, 23));
        assertFalse(events.isEmpty());
        assertEquals(expect, events.get(0).getTitle());
    }
}
  • 지속성 엔티티 ‘event’에 기본키 포함이 필요하나 누락되어있어 테스트가 실패하는 문제가 있었습니다.
    • 이벤트 객체의 JPA 엔티티 정의시 기본키를 추가하여 데이터베이스에서 각 레코드를 고유하게 식별하게 만들어줌으로써 해결했습니다.
    • Event 엔티티 클래스에 기본 키를 추가하여, EventRepositoryTest가 성공적으로 실행될 수 있도록 수정했습니다.
    • JPA 엔티티 사용을 이유로 spring대신 jarkarta.annotation.id를 선택하여 어노테이션 중복 문제를 해결했습니다.

컨트롤러 테스트 && 뷰 추가

  • 컨트롤러 추가
@Controller
public class CalenderController {

    @Autowired
    private EventRepository eventRepository;

    @GetMapping("/calender")
    public String getCalender(Model model) {
        List<Event> events = eventRepository.findAll();
        model.addAttribute("events", events);
        return "calender";
    }

    @PostMapping("/add-event")
    public String addEvent(@RequestParam String title, @RequestParam String date) {
        Event event = new Event();
        event.setTitle(title);
        event.setDate(LocalDate.parse(date));
        eventRepository.save(event);
        return "redirect:/calender";
    }
}
  • 컨트롤러 테스트
@SpringBootTest
@AutoConfigureMockMvc
class CalenderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private EventRepository eventRepository;

    @BeforeEach
    void setUp() {
        eventRepository.deleteAll();
    }

    @Test
    public void testGetCalender() throws Exception {
        mockMvc.perform(get("/calender"))
                .andExpect(status().isOk())
                .andExpect(view().name("calender"));
    }



}
  • 뷰 추가
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Family Scheduler</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <h1>Family Scheduler</h1>
    <form method="post" action="/add-event">
        <div class="form-group">
            <label for="title">Event Title:</label>
            <input type="text" id="title" name="title" class="form-control" required>
        </div>
        <div class="form-group">
            <label for="date">Date:</label>
            <input type="date" id="date" name="date" class="form-control" required>
        </div>
        <button type="submit" class="btn btn-primary">Add Event</button>
    </form>
    <h2>Upcoming Events</h2>
    <ul>
        <li th:each="event : ${events}">
            <span th:text="${event.title}"></span> on
            <span th:text="${event.date}"></span>
        </li>
    </ul>
</div>
</body>
</html>
  • 오타로 인한 컨트롤러 조작 미숙, 이벤트 추가 결과 리디렉션 테스트에서 문제가 있어 HTTP에 대해 추가 학습했습니다

UI 변경


시도한 것들 목록

  • GCP
  • 홈서버 포트포워딩
  • duckdns
    배포 시도를 다양하게 하던 중 github 페이지 배포 시도를 위해 파일명을 변경했습니다
  • 뻘짓

결국 AWS EC2를 통해 배포에 성공하고 시연 데모를 제작했습니다

배포 과정은
1. EC2 인스턴스 생성

  • 지역은 서울로 설정
  • 과금을 최대한 피하는 것을 중심으로
  • AMI는 프리티어가 제공하는 Ubuntu LTS로
  • 인스턴스 유형은 t2.micro 설정 했습니다
  1. 키페어 생성
  • 인스턴스 원격 접속을 위해 sshKey를 생성했습니다
  • 네트워크 설정
  • 스토리지 설정
    • 프리티어가 지원해주는 최대 30GB 사용
  • 인스턴스 최종 생성
  1. 보안 그룹 수정

    보안그룹이란?
    AWS 에서 제공하는 방화벽으로 인바운드 규칙, 아웃바운드 규칙이 존재합니다.

인바운드 규칙(inbound) : 외부에서 EC2나 RDS 등의 내부로 접근할때 사용되는 방화벽 규칙
아웃바운드 규칙(outbound) : EC2나 RDS 등의 내부에서 외부로 접근할때 사용되는 방화벽 규칙

  • EC2에 접속해서 서버를 띄우는것이 목적이기 때문에 인바운드 규칙만 설정했습니다

    • 인바운드 규칙 누락이 배포 에러를 만든다는걸 부딪히면서 배웠습니다..
  • 스프링 부트 기반 서버를 열어줄것이기 때문에 사용자 지정으로 8080 포트를 설정해준 뒤 url을 아는 누구나 접속할수있도록 Anywhere-IPv4 로 설정해줍니다.

  • 원격 EC2 인스턴스에 접속할때 사용되는 ssh 관련 방화벽으로 저는 밖에서도 접속할 때가 있으므로 저희집 고정 ip가 아닌 Anywhere-IPv4 로 설정했습니다. 또한 ssh는 기본 포트 연결로 22번 포트가 사용됩니다.

  • HTTP 연결시 사용됩니다.

  • HTTPS 연결시 사용됩니다

  1. 보안 그룹 설정
  • 사용할 EC2에 설정해줬습니다
  1. 발급 받은 ssh 키로 Ec2 우분투 콘솔에 접속
  • ssh 디렉토리 터미널에서 권한 설정
    chmod 400 sshKey.pem

  • 접속
    ssh -i "sshKey.pem" ubuntu@ec2-43-201-104-83.ap-northeast-2.compute.amazonaws.com

  • 이 과정에서 타임아웃 에러가 계속 나와서 아주 많이 고생했습니다..

    • 인바운드 규칙과 아웃바운드 규칙 설정 오류임을 확인하여 재설정후 해결했습니다
  1. 우분투 콘솔에서 git ssh 연동후 git clone 하기
  • 정적 파일 배포는 방식 두가지
  • 1) EC2에서 프로젝트 git clone 후 실행하기
  • 2) 로컬 머신에서 jar 파일하여 EC2에 복사 후 실행
  • 1안 선택했습니다

EC2 git clone 하기 구글 검색시 명령어로 yum을 사용하라는 블로그 글이 많이 나오는데
ubuntu로 진행하는 경우 apt를 사용해야합니다
yum은 Linux 명령어로 보통 리눅스로 많이 EC2를 사용해서 그런 블로그 글이 많은 것 같습니다.
이 글을 보시는 분들은 참고하시기 바랍니다.

6-1. git 설치
sudo apt-get install git
체크
git --version

6-2. 깃헙에서 SSH KEY 생성
cd ~/.ssh ssh-keygen -t rsa -C github계정 메일(example@github.com)

  • .ssh 디렉토리에서 키페어를 생성하게 되고 id_res.pub 파일이 생성되었습니다
  • 체크
    cat id_rsa.pub
    6-3 cat 명령어로 id_rsa.pub 파일을 출력후 이것을 깃헙 ssh에 저장
  • github -> setting -> SSH and GPG keys -> new SSH key
    key 부분에 id_rsa.pub 값 저장
  1. git clone
  • 깃헙 레포지토리로 이동 -> code -> ssh 탭 복사
  • 우분투 터미널에서 프로젝트 디렉토리로 이동 후 클론
    git clone ssh 탭 복사한 값
  • ls로 디렉토리 여부를 체크할 수 있습니다
  1. 내려받은 파일로 빌드하고 jar 파일 실행시켜 스프링 부트 서버 실행시키기
  • sudo apt install openjdk-17-jdk
  • 체크 java --version
  • ./gradlew build
    • 이 때 테스트 실패가 숨겨져있는데 빌드는 통과하는 경우가 있어 나중에 배포 후 다시 고쳐야 했습니다
    • 터미널 명령어로만 모두 테스트하기보다 하나하나 모두 테스트해야함을 느꼈습니다
    • 참고
    • 빌드 성공했다면 build, libs 경로 생겼으니 cd build/libs 로 이동
    • nohup java -jar woorinature-0.0.1-SNAPSHOT.jar &
    • nohub 명령어 뒤에 &를 붙이면 벡그라운드에서 실행한다는 의미로 저희가 EC2 콘솔 접속을 끊더라도 실행파일을 계속 실행하라는 명령어입니다
    • 명령어를 실행시켜도 스프링 부트가 실행되고 있다는 로그가 나오지 않아 실행되는지 잘 모르겠다면
    • 다음의 명령어를 따라하시면 됩니다
    • 우선 ls 명령어로 libs 디렉토리에 nohub.out 파일이 생성됐는지 확인하고 cat nohup.out으로 nohup.out 파일 출력 로그를 확인할 수 있습니다
    • 현재 실행된 jar 파일을 끄고 싶다면 jobs로 현재 백그라운드에서 돌아가고 있는 파일을 확인한뒤
    • fg %(인덱스)로 벡그라운드에 돌아가고 있는 프로그램을 가져올 수 있습니다.
  1. 브라우저 URL로 접속

  2. 결과 : 유저와의 만남

  1. 부검
  • 웬만한건 메이저 플랫폼이 이미 제공하고 있고 기존 익숙한 것에서 디커플링 하기는 쉽지 않다.
profile
우리스러움

0개의 댓글