문제관리 백엔드 서비스는 online-judge프로젝트의 7개 서비스중 3번째 서비스이다.

  1. 인증 서비스 (Backend): 사용자의 회원 가입, 로그인, 로그아웃, 세션 관리 등을 담당
  2. 인증 서비스 (Frontend): 사용자 인터페이스를 제공 (로그인 폼, 회원가입 폼 등)
  3. 문제 관리 서비스 (Backend): 문제의 추가, 삭제, 수정 등을 관리
  4. 문제 관리 서비스 (Frontend): 문제를 보여주고, 문제 추가, 삭제, 수정 등의 인터페이스를 제공
  5. 제출 관리 서비스 (Backend): 사용자의 코드 제출 및 제출 기록을 관리
  6. 제출 관리 서비스 (Frontend): 코드 제출 인터페이스와 제출 기록 확인 인터페이스를 제공
  7. 채점 서비스 (Backend): 제출된 코드를 채점

다른 서비스는 링크를 눌러확인하자.

 

문제관리 백엔드 서비스는 java/vscod가 아닌 kotlin/IntelliJ로 해보기로 했다. (kotlin호환성이 IntelliJ가 훨씬 좋음)

디렉토리 구성은 다음과 같다.

sevity@sevityubuntu:~/workspace/online_judge/problem-service$ tree -I target
.
├── HELP.md
├── log
│   └── application.log
├── mvnw
├── mvnw.cmd
├── pom.xml
├── run.sh
└── src
    ├── main
    │   ├── kotlin
    │   │   └── com
    │   │       └── sevity
    │   │           └── problemservice
    │   │               ├── controller
    │   │               │   └── ProblemController.kt
    │   │               ├── domain
    │   │               │   ├── Problem.kt
    │   │               │   └── ProblemRepository.kt
    │   │               ├── ProblemServiceApplication.kt
    │   │               └── service
    │   │                   └── ProblemService.kt
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── kotlin
            └── com
                └── sevity
                    └── problemservice
                        └── ProblemServiceApplicationTests.kt

18 directories, 13 files

 

문제에 대한 스키마를 다음과 같이 설정했다.

제대로 하려면, real_input, real_output, solution.cpp 등이 추가되어야 하지만, 우선은 간단하게 했다.

CREATE TABLE problems (
  id SERIAL PRIMARY KEY,
  title VARCHAR(100) NOT NULL,
  description TEXT NOT NULL,
  example_input TEXT NOT NULL,
  example_output TEXT NOT NULL
);

 

 

초기 IntelliJ설정

Ultimate버전과 Community버전이 있는데 전자만 SpringBoot관련 기능이 제공된다.

먼저 로컬환경에서 IntelliJ를 실행한 후,  File > New > Project > Spring Initializr를 통해 프로젝트를 로컬에 생성한다.

위의 내용은 프로그램관리 백엔드에 해당하진 않는다. 개요만 참조하자.

그다음 생성된 파일들을 ssh환경으로 원격복사한다.

 

그다음음 다음과 같이 Remote Development > SSH로 들어가서 원격 개발환경을 설정한다.

application.properties 파일을 다음과 같이 설정. 포트번호는 7개 서비스중 3번째라는 의미로 8083으로 부여.

spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USERNAME}
spring.datasource.password=${DATABASE_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
logging.file.name=log/application.log
# without below line, web login is needed.
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
server.port=8083

알고리즘 문제(problem)에 대한 도메인 entity와 repository를 domain이라는 패키지(폴더)에 다음과 같이 작성한다. 위의 DB스키마와 알맞도록 작성.

먼저 Problem entity는 다음과 같이 작성

package com.sevity.problemservice.domain

import javax.persistence.*

@Entity
@Table(name = "problems")
data class Problem(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    @Column(nullable = false)
    val title: String = "",

    @Column(nullable = false)
    val description: String = "",

    @Column(name = "example_input", nullable = false)
    val exampleInput: String = "",

    @Column(name = "example_output", nullable = false)
    val exampleOutput: String = ""
)

JPA로 DB와 연동을 자동화해주는 Repository는 다음과 같이 작성

package com.sevity.problemservice.domain

import com.sevity.problemservice.domain.Problem
import org.springframework.data.jpa.repository.JpaRepository

interface ProblemRepository : JpaRepository<Problem, Long>

 

MVC에서 C에 해당하는 Control 클래스를 아래와 같이 작성

package com.sevity.problemservice.controller

import com.sevity.problemservice.domain.Problem
import com.sevity.problemservice.service.ProblemService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/problems")
class ProblemController(private val problemService: ProblemService) {

    @GetMapping
    fun getAllProblems() = problemService.getAllProblems()

    @GetMapping("/{id}")
    fun getProblem(@PathVariable id: Long): Problem = problemService.getProblem(id)


    @PostMapping
    fun createProblem(@RequestBody problem: Problem): Problem = problemService.createProblem(problem)

    @PutMapping("/{id}")
    fun updateProblem(@PathVariable id: Long, @RequestBody problem: Problem): Problem = problemService.updateProblem(id, problem)

    @DeleteMapping("/{id}")
    fun deleteProblem(@PathVariable id: Long): ResponseEntity<Void> {
        problemService.deleteProblem(id)
        return ResponseEntity<Void>(HttpStatus.NO_CONTENT)
    }

    // Add more methods as needed for CRUD operations
}

여기서 /problems라는 하나의 자원(동사가 아닌명사)에 대해서 GET, POST, PUT, DELETE를 모두 사용하는 RESTFul 권장 패턴을 사용하고 있다. problems로 복수형으로 지칭하는 것도 권장 가이드.

 

실제로 호출해 보는 것은 curl을 써도 되지만, postman을 쓰면 편하게 할 수 있다.

postman은 여기서 다운받고,

실행후 new > HTTP를 통해서 GET, POST, PUT, DELETE를 하나씩 테스트 하면된다.

GET은 문제 전체나 특정 id를 받아올 수 있고 아래처럼 url만 지정하고 SEND하면 된다.(결과에서 200 OK확인)

POST는 문제를 등록하는 과정이고, 

headers 탭에 Key: Content-Type, Value: application/json 을 추가하고

body에서 raw를 선택하고 아래처럼 문제내용을 입력하고 SEND해주면 된다.(결과에서 200OK확인)

PUT은 문제를 업데이트 하는 과정이고 POST와 마찬가지 설정으로 하면 된다.(POST, PUT만 다르고 나머진 동일)

DELETE는 지우고자 하는 문제ID를 url끝에 넣어주기만 하면 되고, 200OK가 아닌 204 No Content가 나오면 성공.

 

MVC에서 M과 C를 연결하는 비즈니스 로직에 해당하는 Service클래스를 아래와 같이 작성

package com.sevity.problemservice.service

import com.sevity.problemservice.domain.Problem
import com.sevity.problemservice.domain.ProblemRepository
import org.springframework.stereotype.Service

@Service
class ProblemService(private val problemRepository: ProblemRepository) {

    fun getAllProblems(): List<Problem> = problemRepository.findAll()
    fun createProblem(problem: Problem): Problem = problemRepository.save(problem)
    fun getProblem(id: Long): Problem = problemRepository.findById(id).orElseThrow { NoSuchElementException("Problem not found") }
    fun updateProblem(id: Long, problem: Problem): Problem {
        val existingProblem = problemRepository.findById(id).orElseThrow { NoSuchElementException("Problem not found") }
        val updatedProblem = existingProblem.copy(
            title = problem.title,
            description = problem.description,
            exampleInput = problem.exampleInput,
            exampleOutput = problem.exampleOutput
        )
        return problemRepository.save(updatedProblem)
    }
    fun deleteProblem(id: Long) {
        val existingProblem = problemRepository.findById(id).orElseThrow { NoSuchElementException("Problem not found") }
        problemRepository.delete(existingProblem)
    }

}

 

트러블슈팅


원격으로 열었더니 ._로 시작하는 중복파일들이 생길 경우

._로 시작하는 파일들은 macOS에서 생성하는 메타데이터 파일들이다. 이 파일들은 macOS 외의 시스템에서는 필요하지 않으며 삭제해도 안전하다. 다음 명령으로 삭제

find . -name '._*' -type f -delete

1회 지운 이후로 문제가 생긴적은 없다.

 

javax.persistance 관련 오류

springboot 버전을 3이상으로 했더니, import javax.persistance 에 실패하는 현상 발견됨

여기 참고해서, pom.xml에서 아래처럼 2.5.3으로 버전을 낮춰서 해결

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

 

 

 

반응형

+ Recent posts