해당 예제는

01. RESTful 개념과 사전 지식

에서 정리하였던 일부 내용들을 복습하는 용도로 작성된 글입니다.

 

 

 

Spring RESTful API


개발환경 구성하기

 

프로젝트 생성

https://start.spring.io/

기호에 맞게 Maven, Gradle, Java 버전 등을 선택하시면 됩니다.

 

사용되는 의존성은

  • Spring Web
  • Lombok
  • H2 Database
  • Validation
  • Spring Data JDBC입니다.

 

해당 프로젝트는 단일 Entity를 가지는 단순한 RESTful API 예제입니다.

 

HATEOAS를 만족시키진 않았습니다. 해당 내용과 관련해서 인프런에 백기선 님의 RESTful 강의를 수강해보시길 추천드립니다.

 

 

 

Entity, DTO 만들기


@Builder
@Getter
public class Notice {

    private final Long id;
    private final String author;
    private final String title;
    private final String content;
    private final LocalDateTime createDate;
    private final LocalDateTime modifyDate;

}

Builder 패턴과 Getter, final을 사용하여 불변 객체로 만듭니다. 이를 통해 변경될 수 있는 지점을 제거하고, 별도 동기화 없이 멀티스레드 환경에서 안전하게 사용하는 것이 주목적입니다.

 

 

불변 객체를 만드는 방법?

  • 모든 필드를 Final로 만듭니다.
  • 모든 필드를 비공개(Private)로 설정합니다.
  • 필드에 대한 접근자(Getter)만을 제공합니다.
  • 필드에 대한 변경자(Setter)를 제공하지 않습니다.
  • 컬랙션이나 Date(변경되는 객체 등)를 사용하는 필드에 대하여서는 복사본이나 수정 불가능한 타입의 구현체로 반환합니다. Collections.UnmodifiableCollection 등

 

모든 코드가 멀티스레드에 취약한 것은 아닙니다. 상태를 가지는 싱글톤 객체를 사용할 때 , 다른 스레드가 가시 할 수 있는 필드 등에서 여러 변경을 시도하는 경우 문제가 발생할 수 있습니다.

 

 

해당 코드에서 사용된 Lombok Annotation

  • @Builder : 객체 생성 방식을 빌더 패턴으로 제공합니다. 몇몇 다른 라이브러리 관점에서는 (Mybatis, Jackson 등) AllArgumentContructor와 동일한 취급을 받습니다.

      // example
      Notice notice = Notice.builder()
                                          .author()
                                          .title()
                                          .content()
                                          .createDate()
                                          .modifyDate()
                                          .build();
  • @Getter : 현재 객체의 필드에 대한 Getter Method를 생성해줍니다. 생성되는 메서드 이름은 Java Bean Properties를 따릅니다. (getXxx, setXxx, isXxx, hasXxx)

 

 

 

CreateNoticeDto

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class CreateNoticeDto {

    @NotBlank
    private String author;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

    public Notice toEntity() {
        return Notice.builder()
                .author(author)
                .title(title)
                .content(content)
                .createDate(LocalDateTime.now())
                .modifyDate(LocalDateTime.now())
                .build();
    }
}

이 DTO는 Client에서 넘어온 요청을 담는 객체입니다. 해당 프로젝트에서도 별도의 처리 없이 Data Transfer Object의 역할만을 담당하여 Controller와 Service를 이동하게 됩니다.

 

toEntity라는 Converting 메서드가 존재하는데요. 해당 DTO는 Service단에서 Entity로 변환되고 DAO로 접근하게 됩니다. 해당 메서드가 Entity에 있지 않고 DTO에 있는 이유는 Client와 밀접한 관계를 가지는 DTO의 요구사항 변경이 Entity에 영향을 주는 것을 방지하는 목적으로 작성하게 되었습니다.

 

 

해당 코드에서 사용된 Lombok Annotation

  • @AllArgsConstructor : 객체의 모든 필드를 가지는 생성자를 만드는 어노테이션입니다.
  • @NoArgsConstructor : 인자가 없는 생성자를 만드는 어노테이션입니다.

 

해당 코드에서 사용된 javax의 validation Annotation

  • @NotBlank : String에 적용되는 Annotation으로 null, "", " "인지 확인합니다. 조건이 충족된다면 MethodArgumentNotValidException이 발생하게 됩니다.

    문자열과 관련해서 NotNull과 NotEmpty가 합쳐진 Annotation이라고 보아도 무방합니다.

 

MethodArgumentNotValidException?

해당 Exception은 Validation Annotation에 의해 발생하게 되는 RuntimeException으로 특이하게 BindingResult 타입의 필드 변수가 존재하는데요. 해당 변수에는 Validation을 통과하지 못한 필드 명과 메시지가 저장되게 됩니다.

 

 

 

UpdateNoticeDto

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UpdateNoticeDto {

    @NotBlank
    private String author;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

}

해당 DTO는 위와 다르지 않으므로 넘어가겠습니다.

 

 

 

DAO 만들기


해당 프로젝트는 빠른 시작(?)을 위하여서 JDBCTemplate를 사용하였습니다.

 

Notice를 저장하기

public int save(Notice notice) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        final String sql = "INSERT INTO NOTICE(AUTHOR, TITLE, CONTENT, CREATE_DATE, MODIFY_DATE) VALUES(?, ?, ?, ?, ?)";

        jdbcTemplate.update(con -> {
            PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, notice.getAuthor());
            ps.setString(2, notice.getTitle());
            ps.setString(3, notice.getContent());
            ps.setString(4, String.valueOf(notice.getCreateDate()));
            ps.setString(5, String.valueOf(notice.getModifyDate()));
            return ps;
        }, keyHolder);

        return Objects.requireNonNull(keyHolder.getKey()).intValue();
    }

CRUD 중 C! 객체를 DB에 저장하는 로직입니다. 단순히 저장만 할 것이라면 쿼리와 인자만을 사용해도 되지만, 위와 같이 Key(Notice ID) 값을 클라이언트에게 넘겨줌으로써 그에 대한 정보나,  상태를 가질 수 있도록 할 수 있습니다.

 

예를 들어 HATEOAS를 지원하는 RESTful API라면, 현재 호출된 URI가 api/notices 일 텐데요. LinkBuilder를 이용하여서 클라이언트에게 api/notices/{생성된 id}를 전달할 수 있고, 클라이언트는 이 링크를 사용만 함으로써 쉽게 생성된 정보를 확인할 수 있는 페이지로 전환 가능할 것입니다.

 

 

Notice를 조회하기 + RowMapper

public List<Notice> findAll(Long page, Long offset) {
        final String sql = "SELECT * FROM NOTICE ORDER BY CREATE_DATE DESC LIMIT " + page + " OFFSET " + offset;
        return jdbcTemplate.query(sql, rowMapper());
}

private RowMapper<Notice> rowMapper() {
        return (rs, rowNum) -> Notice.builder()
                .id(rs.getLong("ID"))
                .author(rs.getString("AUTHOR"))
                .title(rs.getString("TITLE"))
                .content(rs.getString("CONTENT"))
                .createDate(rs.getTimestamp("CREATE_DATE").toLocalDateTime())
                .modifyDate(rs.getTimestamp("MODIFY_DATE").toLocalDateTime())
                .build();
}

CRUD 중 R! DB에서 조건에 맞는 객체를 가져와 클라이언트에게 전달하는 로직입니다. 쿼리에 LIMIT와 OFFSET을 이용함으로써 간단한 페이징 기능을 구현하였습니다.

 

RowMapper는 반환되는 ResultSet을 객체로 변환하는 로직을 구현하는 것입니다. 각각의 결과 행들을 Mapping 합니다.

 

 

Notice를 수정하기

public int updateById(UpdateNoticeDto noticeDto, Long noticeId) {
        final String sql = "UPDATE NOTICE SET AUTHOR = ?, TITLE = ?, CONTENT = ?, MODIFY_DATE = ? WHERE ID = ?";
        return jdbcTemplate.update(sql, noticeDto.getAuthor(), noticeDto.getTitle(),
                noticeDto.getContent(), LocalDateTime.now(), noticeId);
}

CRUD 중 U! Client에서 전달한 notice의 수정 정보를 DB에 반영하는 로직이 작성되어 있습니다.

 

 

Notice를 삭제하기

public int deleteById(Long id) {
        final String sql = "DELETE FROM NOTICE WHERE ID = ?";
        return jdbcTemplate.update(sql, id);
}

마지막으로 CRUD 중 D! Client에서 전달한 Notice ID를 삭제하는 로직이 작성되어 있습니다. 현재 작성된 로직은 Hard Delete 방식으로 바로 데이터를 삭제하게 되는데, 다른 방법으로는 Soft Delete라는 것이 존재합니다.

 

Soft Delete는 테이블 칼럼의 Flag를 변경하여 최종 결과에서 필터링하거나, 별도의 테이블에 데이터를 이동시켜 관리하는 것으로, Batch와 조건식을 통해서 일정 시간, 일정 상황에 데이터가 삭제되게끔 작성할 수 있습니다.

 

 

Domain Service 만들기


해당 프로젝트에서 Service는 단순하게 Controller와 DAO를 분리하는 Layer의 용도로만 작성되었습니다. 해당 내용에 대해서는 계층형 아키텍처를 참고하는 것이 좋습니다.

 

Service Layer의 주요 목적은 비즈니스 로직 수행, 트랜잭션 관리(글로벌 트랜잭션 경계 설정 등), 접근 권한 확인, Controller와 DAO의 결합 분리 등이 있다고 생각합니다.

 

Service에서 save 호출하기

@Service
public class NoticeService {

    private final NoticeDao noticeDao;

    public NoticeService(NoticeDao noticeDao) {
            this.noticeDao = noticeDao;
    }

    public int save(CreateNoticeDto noticeDto) {
            Notice notice = noticeDto.toEntity();

            int result = noticeDao.save(notice);
            if (isNotReflected(result)) {
                    throw new RuntimeException("Notice save Failed");
            }
            return result;
    }

크게 복잡한 로직은 존재하지 않습니다. DTO를 Entity로 Converting 하고 DAO의 save를 호출합니다. 호출된 결과를 int 값으로 받게 되는데, 1 이상이 아니라면 결과가 반영되지 않았으므로 Runtime Exception을 발생시키게 됩니다.

Service 로직에서 발생하는 Exception들은 이후 ExceptionHandler를 통해 Handling 합니다.

@Service Annotation은 Component를 확장한 MetaAnnotation 중 하나로 ComponentScan의 대상입니다.

 

 

Service에서 findAll 호출하기

public List<Notice> findAll(Long page, Long offset) {
        return noticeDao.findAll(page, offset);
}

Dao의 findAll을 호출하고 그 결과를 Controller로 전달합니다.

 

 

Service에서 update 호출하기

public void updateById(UpdateNoticeDto noticeDto, Long noticeId) {
        if (isNotReflected(noticeDao.updateById(noticeDto, noticeId))) {
            throw new RuntimeException("Notice update Failed");
        }
}

save와 마찬가지로 로직을 호출하고 그 반영 결과를 검증하여 Runtime Exception을 발생시킵니다.

 

 

Service에서 delete 호출하기

public void deleteById(Long noticeId) {
        if (isNotReflected(noticeDao.deleteById(noticeId))) {
            throw new RuntimeException("Notice delete Failed");
        }
}

update와 동일함으로 넘어가겠습니다.

 

 

Service의 결과 검증 메서드

private boolean isNotReflected(int result){
        return result < 1;
}

 

 

 

 

Domain Controller 만들기


 

Controller의 save

@RestController
@RequestMapping("/api")
public class NoticeController {

    private final NoticeService noticeService;

    public NoticeController(NoticeService noticeService) {
        this.noticeService = noticeService;
    }

    /**
     * 사용자의 글 작성
     *
     * @return 작성된 notice 에 대한 id 반환
     * @author lob
     */
    @PostMapping("/notices")
    public ResponseEntity<NoticeInfo> createNotice(@Valid @RequestBody CreateNoticeDto noticeDto) {

        int result = noticeService.save(noticeDto);
        return ResponseEntity.status(HttpStatus.OK).body(new NoticeInfo(result, "notice created"));
    }

Controller 코드의 최상단입니다. Class Level에는 @RequestMapping("path 정보")를 적용하여 모든 메서드의 URL prefix를 설정하였습니다. 이후 NoticeService를 생성자 주입으로 DI 받고, 메서드를 호출합니다.

 

createNotice는 @PostMapping("/notices")로 설정되어 있는 상태인데, 이는 POST.../api/notices 형태의 요청과 Mapping 되는 것임을 나타냅니다.

 

메서드의 인자로 DTO를 받고 해당 DTO에 대한 @Valid와 @RequestBody를 적용하였습니다. 이는 json으로 된 요청 정보를 DTO 생성 후 매핑하고, DTO Field Level에 설정된 Valid Annotation을 통해 Validation을 진행한다는 것을 나타냅니다. @NotBlank, NotEmpty..

 

json Mapping 정보를 DTO로 Mapping 할 때 필드가 private로 캡슐화되어 있는 상태라면, Getter를 통해 필드 이름을 특정하고 직렬 화합니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @RestController : Controller와 ResponseBody 기능이 Annotation입니다.

    • @Controller는 요청을 받고 결과를 반환하는 역할을 하는 Bean을 등록할 때 사용되며 내부적으로 Component Annotation이 적용되어 있습니다.

    • @ResponseBody는 컨트롤러가 반환하는 결과를 Http Message Body에 저장합니다.

      JSON 형식을 반환한다라는 글들이 많은데, 실제로는 HTTP Header의 Content-type 값을 따릅니다. 즉 byte 값, XML, TEXT 등으로도 반환된다는 것입니다.

  • @PostMapping : HTTP POST Method 형식을 처리하는 것을 나타내는 Annotation입니다. 이는 RequestMapping을 확장한 것이며, URL 값을 나타내는 value, Headers, 요청 타입을 협상하고 반환 타입을 지정하는 produces 필드를 자주 사용하게 됩니다.

  • @RequestBody : 요청된 HTTP Message Body에 저장된 값을 직렬 화하여 객체로 변환하는 데 사용되는 Annotation입니다. JSON, XML, Text 등을 가져올 수 있습니다.

  • @Valid : Object 필드에 선언된 Valid 조건들을 검증하도록 하는 Annotation입니다.

 

 

Controller의 findAll

/**
     * @return 작성된 순서대로 10개씩 notice 반환
     * @author lob
     */
    @GetMapping("/notices")
    public ResponseEntity<List<Notice>> findAll(
            @RequestParam(defaultValue = "10", required = false) Long page,
            @RequestParam(defaultValue = "0", required = false) Long offset) {

        List<Notice> notices = noticeService.findAll(page, offset);
        if (CollectionUtils.isEmpty(notices)) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }
        return ResponseEntity.status(HttpStatus.OK).body(notices);
}

findAll은 @GetMapping("/notices")으로 설정된 상태인데 이는 GET.../api/notices 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @GetMapping *: HTTP GET Method 형식을 처리하는 것을 나타내는 Annotation입니다. 이것도 RequestMapping을 확장한 Annotation입니다.*

  • @RequestParam : HTTP URL에 붙어서 날아오는 QueryString을 변수에 매핑하는 Annotation입니다. defaultValute를 통해 요청에 담겨오지 않는 경우의 값을 설정할 수 있으며, required를 통해 요청에 QueryString 존재 유무에 따라서 Exception을 발생시킬지를 설정할 수 있습니다.

    required의 기본 값은 True입니다.

 

 

Controller의 update

    /**
     * @return notice 수정 후 안내 문자열 반환
     * @author lob
     */
    @PutMapping("/notices/{noticeId}")
    public ResponseEntity<String> updateById(@Valid @RequestBody UpdateNoticeDto noticeDto,
                                             @PathVariable Long noticeId) {

        noticeService.updateById(noticeDto, noticeId);
        return ResponseEntity.status(HttpStatus.CREATED).body("Notice Updated");
    }

updateById은 @PutMapping("/notices/{noticeId}")으로 설정된 상태인데 이는 PUT.../api/notices 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @PutMapping : HTTP PUT Method 형식을 처리하는 것을 나타내는 Annotation입니다. RequestMapping을 확장하였습니다.

 

 

Controller의 delete, NoticeInfo Object

    /**
     * @return notice 삭제 후 안내 문자열 반환
     * @author lob
     */
    @DeleteMapping("/notices/{noticeId}")
    public ResponseEntity<String> deleteById(@PathVariable Long noticeId) {
        noticeService.deleteById(noticeId);
        return ResponseEntity.status(HttpStatus.OK).body("Notice Deleted");
    }

    @Getter
    @AllArgsConstructor
    private static class NoticeInfo {
        private final int NoticeId;
        private final String message;
    }

deleteById은 @DeleteMapping("/notiecs/{noticeId}")으로 설정된 상태인데 이는 Delete.../api/notieces 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @DeleteMapping : HTTP DELETE Method 형식을 처리하는 것을 나타내는 Annotation입니다. RequestMapping을 확장하였습니다.

 

 

Exception Handler 적용해보기


@ControllerAdvice("com.example.rest.notice")
public class NoticeExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    protected ResponseEntity<ErrorResponse> HandlerRuntimeException(RuntimeException exception) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse(exception.getMessage()));
    }

    @Getter
    @AllArgsConstructor
    private static class ErrorResponse {
        private final String errorMessage;
    }
}

간단하게 작성한 Handler입니다. Service에서 발생하는 RuntimeException에 의한 White Page를 방지하고, Client에게 별도의 데이터를 제공합니다.

예제의 간소화를 위하여 RuntimeException 형식만을 지정하였습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @ControllerAdvice : Spring Application에서 전역적인 예외 처리를 위해 사용되는 객체에 적용하는 Annotation입니다. Controller에서 결과를 반환한 이후 즉 AfterReturning 시점에서 적용되며, 내부에 정의된 ExceptionHandler 설정에 따라 처리하게 됩니다.

    Class Level Annotation이며, 특정 Package에만 적용하는 것이 가능하고 Order Annotation을 통해 적용 우선순위도 지정할 수 있습니다.

  • @ExceptionHandler : Spring Application에서 특정 예외 처리를 위해 사용되는 Method Level의 Annotation입니다. 기본적으로 @ExceptionHandler(XxxException.class) 형식으로 정의되어 해당 Exception을 가로채고 매개변수로 받아올 수 있습니다.

 

 

02-01 12 : 58 추가

간단한 MockMvc Test 작성해보기


@SpringBootTest
@AutoConfigureMockMvc
class NoticeControllerTest {

	@Autowired
	MockMvc mockMvc;

	@Autowired
	ObjectMapper objectMapper;

	CreateNoticeDto createNoticeDto;
	UpdateNoticeDto updateNoticeDto;

	@BeforeEach
	void setUp() {
		createNoticeDto = new CreateNoticeDto("author", "title", "content");
		updateNoticeDto = new UpdateNoticeDto("update", "update", "update");
	}

	@Test
	void noticeControllerTest_createAndFind() throws Exception {

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(get("/api/notices")
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());

	}

	@Test
	void noticeControllerTest_createAndFindAndUpdate() throws Exception {

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(put("/api/notices/{noticeId}", 1L)
				.content(objectMapper.writeValueAsString(updateNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());

		mockMvc.perform(get("/api/notices")
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());
	}

	@Test
	void noticeControllerTest_deleteById() throws Exception {

		mockMvc.perform(delete("/api/notices/{noticeId}", 2L)
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());
	}

}

 

 

코드 설명 추가 예정

 

 

 

지금까지 간단하게 Controller부터 Service, Dao까지 구현해보았습니다. 

 

 

관련된 기능, 어노테이션에 대하여서 추가적인 예제가 필요하신 분들은

www.baeldung.com/rest-with-spring-series

 

REST with Spring Tutorial | Baeldung

Step by step tutorial on building a REST API with Spring (and securing it with Spring Security).

www.baeldung.com

해당 사이트를 확인해보시길 바랍니다.

 

RESTful Tutorial!


이 글은 RESTful에 대하여서 완벽하게 정리하는 것이 아닌 기본적인 내용을 학습하도록 작성한 글입니다.

 

세세한 내용에 대해서는 각 목차를 Keyword로 검색하시길 바랍니다.

 

 

 

해당 예제들은 다음 글에서 업로드할 예정입니다.

 

 

Notion에서 작성하고 옮겨오다 보니 몇몇 양식이 깨져있을 수 있습니다. 그렇기에 Notion Link도 남겨드립니다. www.notion.so/Week_02-01-REST-8d07b91683d548c2aa4e20f2f404eeef

 

 

RESTful을 위한 사전 지식


HTTP (Hypertext Transfer Protocol)

Web client와 Server 간의 데이터 전송을 위해 사용되는 Application Layer Protocol입니다.

 

요청과 응답을 하나의 트랜잭션 단위로 묶어놓고, 응답 이후에는 별도의 정보나 상태를 가지지 않는 Stateless Protocol이고, 데이터를 평문으로 전송되게 구현되어 있습니다.

 

 

사용 시 고려해야 할 점

  • Stateless 하기에 상대적으로 서버의 Resource를 적게 사용할 수도 있으나, TCP Connection에 대한 Overhead가 심화될 수 있기에 이를 최적화하기 위한 여러 방법을 고려해야 합니다.
  • 데이터가 평문으로 전송되기에 패킷 탈취, 변조에 대한 보안 공격에 취약점을 가지기에, HTTP Over SSL(HTTPS)과 같은 방법을 사용하여야 합니다.

 

추가적으로 학습할 Keyword로는 Handshaking, HTTP 구조와 1.1, 2.0, quic 정도가 있습니다.

 

 

HTTP Method

 

  • GET (멱등성 O)
  • Resource를 조회하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 기본적으로 제공되지 않습니다. 이는 요청 Body를 이용할 수 있음을 의미합니다. 
  • 몇 번을 요청하더라도 호출의 결과는 같기에 멱등성을 지킨다고 이야기합니다. 이 요청을 통해 받을 수 있는 State code는 200, 400, 404 등이 있습니다.

 

  • POST (멱등성 X)
  • Resource를 생성하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 존재합니다. 해당 요청 시 Resource를 만들 수 있는 충분한 정보를 가져야 합니다.
  • 요청 시마다 새로운 Resource가 생성되기에 멱등성을 지키지 못한다고 이야기합니다. 받을 수 있는 State code는 201, 204 등이 있습니다.

 

  • DELETE (멱등성 O, X)이 요청은 구현 방식에 따라 멱등성을 지킬 수도 있고, 지키지 못할 수도 있습니다. 기본적인 제약으로는 멱등성을 지킨다고 하는데, 이를 만족시키기 위해서는 해당 요청에 따라 Resource가 바로 삭제되는 것이 아닌 Flag 등을 통한 Soft Delete가 되어야 합니다.
  • Resource를 삭제하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 불가능하고 요청 Body가 존재하지 않습니다.

https://tools.ietf.org/html/rfc7231#section-4.3.5

If the target resource has one or more current representations, they
might or might not be destroyed by the origin server, and the
associated storage might or might not be reclaimed, depending
entirely on the nature of the resource and its implementation by the
origin server (which are beyond the scope of this specification).

 

  • PUT (멱등성 O)
  • Resource를 전체적으로 수정할 때 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 불가능하고 요청 Body가 존재합니다.
  • 이 요청은 Resource가 존재한다면 모든 정보를 수정하고, 존재하지 않는다면 새로운 Resource를 생성하게 됩니다. 즉 POST와 같이 Resource 생성 시 필요한 모든 정보를 포함하고 있어야 합니다.

 

  • PATCH (멱등성 X)
  • Resource의 일부 정보를 수정하기 위해 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 존재합니다.
  • 이 요청은 Resource가 존재한다면 일부 정보를 수정하고, 존재하지 않는다면 Update 되지 않을 수 있습니다. 매 실행마다 다른 결과를 받을 가능성이 존재하기에 기본적으로 멱등하지 않다고 이야기합니다.
  •  
  • 해당 방식을 PUT과 동일한 방식으로 작성한다면 멱등성을 가질 수 있게 될 수도 있습니다. https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/PATCH

 

이외에도 알아볼 Keyword로는 HEAD, CONNECT, OPTIONS, TRACE 등이 있습니다.

 

 

HTTP Status

HTTP Protocol은 Status Code를 정의, 제공하기에 요청 Client에게 State를 알려줄 수 있습니다.

 

 

1xx 번대

  • 100 Continue
  • 임시 응답, Client 가 요청하거나 요청이 완료된 경우에는 무시해도 되는 코드입니다.
  • 101 Switching Protocol
  • 서버에서 통신 Protocol을 변경할 것임을 알려줍니다.
  • 102 Processing
  • 서버가 요청에 대해서 처리 중이지만, 아직 응답할 수 없음을 알려주는 코드입니다.

 

2xx 번대

  • 200 OK
  • 메서드에 따라 의미가 변경되는 code로써 일반적으로 요청이 성공적으로 진행되었다는 의미입니다.
  • 201 Created
  • 요청이 성공적이었고, 그에 따른 새로운 Resource가 생성되었다는 의미입니다.
  • 204 No Content
  • 요청은 성공적이었으나, 서버에서 제공할 Content는 존재하지 않는다는 의미입니다.

 

3xx 번대

  • 300 Multiple Choice
  • 요청에 대해 하나 이상의 응답이 가능함을 의미하고, Client는 응답 방식을 선택하여야 합니다.
  • 301 Moved Permanently
  • 요청한 Resource의 URI가 변경되었음을 의미합니다.
  • 303 See Other
  • 요청한 Resource에 대해 다른 URI로 GET 요청을 보내야 함을 알려주는 것입니다.

 

4xx 번대

  • 400 Bad Request
  • 잘못된 요청 정보에 의하여 서버가 해당 요청을 처리할 수 없음을 의미합니다.
  • 401 Unauthorized
  • 해당 요청이 인증되지 않았음을 의미합니다. 이 Resource에 접근하기 위해서는 인증이 필요합니다.
  • 403 Forbidden
  • 해당 요청이 인가되지 않았음을 의미합니다. 이 Resource에 접근하기 위한 권한이 부족합니다.
  • 404 Not Found
  • 해당 요청에 대해서 해당하는 Resource를 찾지 못했음을 의미합니다.
  • 405 Method Not Allowed
  • 해당 요청에 따른 메서드가 존재하나, 현재 사용할 수 없음을 알려줍니다. 제거된 경우도 포함합니다.

 

5xx 번대

  • 500 Internal Server Error
  • 서버가 현재 요청을 처리하지 못하는 상황이거나, 처리할 수 없는 요청임을 의미합니다.
  • 502 Bad Gateway
  • 서버가 요청을 처리하는데 필요한 응답이 잘못 수신되었음을 의미합니다.
  • 503 Service Unavailable
  • 서버가 요청을 처리할 준비가 되지 않은 상태임을 의미합니다.

 

 

API

운영체제나 프로그래밍 언어가 제공하는 기능 등에 대해 제어할 수 있게 만든 인터페이스입니다.

 

API의 종류

  • Private API
  • 자사 제품, 서비스를 개선하기 위해 내부적으로 발행(구현)하는 API입니다. 외부에 노출되지 않습니다.
  • Public API
  • 개방형 API로써 모두에게 공개되는 API입니다. Kakao OpenBuilder나 Map API 등이 있습니다.
  • Partner API
  • 기업이 데이터 공유에 동의한 특정 기업, 사용자들에게 제공하는 API를 말합니다. B2B Solution가 이에 속할 수 있습니다.

 

 

Resource

리소스는 컴퓨터, 서버에 저장할 수 있는 모든 것을 포함할 수 있습니다.

 

이는 작성된 페이지, 문서, 이미지와 같은 Static Resource와 요청에 따른 논리적인 결과 값인 Dynamic Resource를 포함하는 개념입니다.

 

 

추가 학습 자료 : https://geobgu.xyz/web-mapping/web-servers-1.html#overview

 

 

URI

서버에서 제공하는 Resource는 식별자로 관리되고 접근할 수 있습니다. 이를 URI라고 합니다.

 

Client는 URI를 이용하여 서버에게 필요한 Resource에 대한 제공 요청을 할 수 있습니다.

 

 

URI 종류

  • URL (Uniform Resource Locator)http://www.oreilly.com/index.html , http://www.naver.com/index.html
  • URL은 특정 서버의 한 Resource에 대한 구체적인 위치를 서술합니다.
  • URN (Uniform Resource Name)urn:examle:index
  • Resource의 위치에 영향받지 않는 유일무이한 식별 값의 역할을 합니다.

 

 

Represent

표현은 Client에게 Resource를 어떻게 제공할 것인지에 대한 개념입니다.

 

서버는 XML, JSON, CSV, YAML, TEXT 등 여러 Format을 Client의 요청에 맞게 표현하여 제공해야 합니다. Client는 Content-Type Header를 통해 제공받을 Format을 정의할 수 있습니다.

 

 

JSON

JavaScript Object Notation은 데이터를 저장, 전송할 때 사용되는 경량 Data Format입니다.

 

JavaScript 에서 작성되는 Object 형식을 사용하였기에 JSON이라고 명명되었습니다.

 

 

Format Example

{
    "id" : "1",
    "name" : "lob",
  "email" : "test@email.com"
}

이와 같이 Key : Value Format을 사용합니다. 추가적으로 UTF-8 Encoding을 제공하며, 별도의 주석을 제공하지 않습니다.

 

 

Client-Server Model

Resource 요청자와 Resource 제공자 간에 작업을 분리하는 분산 애플리케이션이자 네트워크 아키텍처를 의미합니다. 일반적으로 웹 페이지 ↔ 서버 관계를 말합니다.

 

이외에도 알아볼 Keyword로는 DNS, Router, TCP/IP, UDP, Packet 등이 있습니다.

 

 

 

 

REST이란? (REpresentational State Transfer)


REpresentational State Transfer : 리소스의 상태를 표현하고 전송한다.

 

REST란 WWW과 같은 분산 하이퍼미디어 시스템에서 리소스와 상태를 표현하고, 전송하는 것에 대한 설계 양식입니다.

 

많은 사람들은 HTTP Protocol을 통해 REST를 구현하고 있지만, 이것은 HTTP에 종속되지 않는 개념입니다. 즉 다른 Protocol을 사용하거나 새로운 Protocol을 만들더라도 해당 설계 원칙을 지키면 "RESTful 하다."라고 말할 수 있음을 의미합니다.

 

 

해당 내용에 대해서는 CoAP RESTful API를 검색해보시면 좋을 것 같습니다.

 

 

 

REST의 6대 제약


Client-Server Model

Client - Server 아키텍처와 같이 독립적인 상태에서 구현되어야 하는 API 규약입니다.

 

 

Stateless

매 요청은 필요한 모든 정보를 담고 있어야 하며, 종료 시 상태는 없어야 한다.

 

 

Cache

SELECT - GET과 같은 조회성 트랜잭션은 캐싱하여야 한다.

 

 

Uniform Interface

제공되는 인터페이스는 일반적이고, 일관성이 있어야 하며, 간단할수록 좋습니다.

  • Identification of resourcesREST API Design 항목 참고
  • 각 Resource은 유일하게 식별 가능해야 하며, 개념적으로 분리되어야 합니다.

 

  • Manipulation of resources through representationsClient는 매 요청에 대한 충분한 정보를 제공하여야 합니다. 수정, 조회, 삭제를 위한 데이터
  • HTTP Method로 CRUD라는 표현을 담아야 합니다. URI에 담지 않습니다.

Example

GET "http://www.example.com/api/users" HTTP/1.1

PATCH "http://www.example.com/api/users/1224" HTTP/1.1

이와 같이 URI 정보에는 CRUD 표현을 담지 않고, HTTP Message의 Method를 통해 표현해야 합니다.

 

  • Self-describing messages
  • 메시지 스스로 자신에 대한 설명이 가능해야 합니다.
  • Resource를 제공할 때 Resource의 Type을 제공하고 (Content-type), Resource에 대한 링크를 Response Body에 포함함으로써 해당 조건을 만족할 수 있습니다.

 

  • Hypermedia as the engine of application state (HATEOAS)
  • 하이퍼 링크를 통해서 application의 상태 변화가 가능해야 합니다.
  • Example
{
    ... ,

    "_links": {
        "self": {
            "href" : "http://www.example.com/api/users/1334"
        },
        "prev-link" : {
            "href" : "http://www.example.com/api/users"
        }
    }
}

 

위에 제시된 예시처럼 Link를 제공하여 페이지 이동 등을 할 수 있도록 지원하여야 합니다.

데이터에 대한 링크를 제공하는 것을 HAL이라고 합니다.

 

 

Layered System

계층적으로 구성되어야 하며, Client는 리소스에 직접 접근하지 않고 (Server를) 호출하여야 합니다.

 

이는 각각의 기능이 다른 계층으로 구성되고 각각의 계층은 인접한 계층에만 통신하며, 기능 수행을 위해 하위 계층을 의존해야 한다는 것을 의미합니다.

 

Controller ↔ Service ↔ Repository와 같은 구조도 Layered 아키텍처입니다.

 

 

Code-On-Demand (Optional)

Server가 Client에게 Code(JavaScript..)를 제공하여 확장을 할 수 있어야 합니다.

 

 

 

 

Richardson Maturity Model


이는 REST 제약에 부합되는 정도를 등급으로 매긴 것입니다. 일반적으로 RESTful API이라 명명할 수 있는 Level은 2 Level부터입니다.

 

 

level 0 : The Swamp of POX

HTTP를 데이터 전송을 위한 protocol로만 사용할 뿐 상태를 나타내거나 하지 않습니다.

 

POX 란 순수한, 평범한 XML를 주고받는 것을 의미하며, 이 level은 하나의 Endpoint를 제공하는 단순한 스타일의 API를 이야기합니다. 이러한 시스템을 RPC Model이라고 명칭 하기도 합니다.

 

 

이와 관련된 키워드는 SOAP, XML-RPC, RPC Model 등이 있습니다.

 

 

 

level 1 : Resource

서버의 Resource에 대한 각각의 point를 제공하는 상태의 API를 의미합니다.

 

모든 요청은 필요한 Resource에 대한 개별적인 point로 통신하는 상태이지만, 하나의 HTTP Method만을 사용합니다.

 

 

Example

POST "http://www.example.com/users" HTTP/1.1
POST "http://www.example.com/posts" HTTP/1.1
POST "http://www.example.com/locations" HTTP/1.1

 

 

level 2 : HTTP Verbs

HTTP Method와 Resource 동사를 결합하여 REST API를 온전히 제공하는 단계입니다.

 

HTTP Protocol의 능력을 최대한 활용한 상태라는 것은 각각의 HTTP Method를 골고루 활용하고 있음을 나타냅니다.

 

Example

GET "http://www.example.com/users" HTTP/1.1
POST "http://www.example.com/users" HTTP/1.1
DELETE "http://www.example.com/users/1223" HTTP/1.1
PATCH "http://www.example.com/users/1223" HTTP/1.1

 

 

level 3 : HATEOAS

Client와 동적인 상호작용이 가능한 상태의 REST API를 말합니다. 이는 Link를 통해 제공됩니다.

 

Uniform Interface : Hypermedia as the engine of application state 항목.

 

 

 

 

REST API Design


URI는 제공되는 Resource를 기준으로 작성해야 합니다.

 

 

복수 자원

두 개 이상의 Resource를 제공할 때 작성하는 방식입니다.

                                (post 정보들)
"https://www.example.com/api/posts"

                                (location 정보들)
"https://www.example.com/api/locations"

                                (user 정보들)
"https://www.example.com/api/users"

 

 

단일 자원

하나의 Resource를 제공할 때 작성하는 방식입니다.

                (post들 중에 하나의 post)
"https://www.example.com/api/posts/{postId}"          -> "api/posts/123"

                                (location들 중에 하나의 location)
"https://www.example.com/api/loacations/{locationId}" -> "api/loacations/123"

                                (user들 중에 하나의 user)
"https://www.example.com/api/users/{userId}"          -> "api/users/123"

 

 

하위 리소스 표현

해당 Resource가 보유하고 있는 하위 Resource에 대해 작성하는 방식입니다.

        ( Customer들 중에 하나의 Customer 정보 )의( accounts 정보 들)
"https://www.example.com/api/customers/{customerId}/accounts" 

        ( Customer들 중에 하나의 Customer 정보 )의( accounts 정보 들 중 하나의 accounts  )
"https://www.example.com/api/customers/{customerId}/accounts/{accountsId}"

 

 

버저닝

변경 사항에 따른 API 버전 관리 방식입니다.

 

 

Semantic Versioning

{MAJOR}. {MINOR}. {PATCH} 형식으로 Version을 관리하는 방식을 의미합니다.

 

example) 1.0.1

 

 

버전 관리 방식의 종류

 

 

REST 요소 : Document, Collection, Store, Controller

  • Document위에서 설명된 단일 자원 표현이 이에 해당됩니다.
  • https://www.example.com/api/users/{userId}
  • 기본이 되는 Resource 형식을 의미합니다. 이는 데이터베이스의 Record를 검색하는 것과 유사합니다.
  • Collection위에서 설명된 복수 자원 표현이 이에 해당됩니다.
  • https://www.example.com/api/users
  • Server에서 관리하는 Document들이 모여있는 Store를 의미합니다. 이는 데이터베이스의 Table를 검색하는 것과 유사합니다.
  • Store복수로 작성되어야 합니다. profiles, favorites...
  • https://www.example.com/api/users/favorites
  • Client에서 관리하는 Resource Store를 의미합니다.
  • Controllersign-up, buy
  • https://www.example.com/api/users/sign-up
  • Resource에 대한 CRUD 조작 이외의 의미들을 나타냅니다.

 

 

기타 규약

  • 자원을 나타내는 명사만을 사용하여야 하며, 동사를 사용하지 않습니다.
  • "/"를 통하여 계층 관계를 나타내어야 합니다. (맨 뒤에는 / 를 사용하지 않습니다.)
  • 명사와 명사 사이에는 가독성을 위해 '-' (하이픈) 문자를 사용해야 합니다.
  • '_'을 사용하지 않습니다. 이는 일부 브라우저나 화면에서 보이지 않을 수 있기 때문입니다.
  • 소문자만을 일관적으로 사용하여야 합니다.
  • 파일 확장자를 사용하지 말아야 합니다. 이는 어떠한 이점도 주지 않고 URI 길이만 늘어나게 하기 때문입니다.

 

해당 내용들을 읽은 후 공개 API 설계 시 도움이 될 수 있는 Keyword로는 OAS 3.0이 있습니다.

 

 

 

 

참고 자료


 

 

 

데이터베이스의 index?


Index는 책의 목차, 색인과 같은 역할을 담당하는 데이터베이스 객체로써 테이블과 독립적으로 존재합니다. 하지만 테이블에 의존적이기에 해당 테이블이 삭제될 경우 같이 제거되게 됩니다.

 

존재하는 칼럼의 값과 해당 레코드가 저장된 주소를 키와 값의 구조로 묶어 저장하고, 정렬된 상태를 유지하기에 저장, 수정, 삭제 기능들의 성능을 희생하고 빠른 조회를 제공하는 것이 Index의 사용 목적입니다.

 

 

 

기본 제공되는 Index?

기본적으로 제공되는 Index는 PK index 입니다. InnoDB는 설계상 이유 때문에 모든 테이블에 PK가 필요한데요. 개발자가 테이블에 PK를 작성하지 않는다면, 암시적으로 PK를 생성하여 레코드를 탐색하고 인덱스를 생성하는데 사용되게 됩니다.

 

 

 

 

Primary Key vs Secondary Key

  • Primary Key : 테이블마다 기본 키 제약 조건을 통해 만들어지는 하나의 고유한 Key를 의미하고, Null 값과 중복 값을 허용하지 않으며, 앞서 이야기한 것처럼 자동으로 인덱스를 생성하여 데이터에 대한 빠른 접근을 지원합니다.
  • Secondary Key : 각 레코드에 대한 고유한 값을 제공하는 Key를 의미하는데, Null을 허용하며, 레코드를 식별하는데 사용할 수 있고 Index로 활용할 수 있습니다. 하나의 테이블에 여러 Key가 존재할 수 있습니다.

 

 

 

Unique Index를 사용하는 이유?

Index에도 Unique Index와 Non-Unique Index가 존재하는데요. 간단하게 Unique Index를 알아봄으로써 어떠한 차이를 가지는지 확인해보겠습니다.

  • Unique Index : 하나의 Key만 존재함을 나타냅니다. 이는 Index에 대해 동등 조건을 사용하는 쿼리에서 DB의 쿼리를 실행하고 최적화하는 옵티마이저에게 하나의 Index를 찾았을 때 더 이상 스캔하지 않아도 된다는 의미를 제공하게 되며, 이를 통해 쿼리 최적화를 수행하게 되는데요. 그렇기에 사용 가능한 모든 경우에서는 Unique Index를 사용하는 것이 권장되게 됩니다.

    추가적으로 고유한 인덱스를 사용하는 것은 해당 레코드의 데이터 무결성을 보장하게 됩니다.

 

 

index의 장점?

  • 조회(SELECT) 쿼리의 성능을 향상시킵니다. 여러 서비스에선 데이터를 조회하여 사용자에게 제공하는 것이 다른 쿼리보다 많은 비중을 차지하기 때문에 도입하는 경우도 있습니다..

  • 고유한 index 형식을 사용한다면 이는 행에 대해서도 중복 없이 구성하는 것을 보장합니다.

  • 매번 테이블을 스캔한 후 행을 정렬하는 절차를 생략하게 합니다. 미리 정렬된 목록을 제공함으로써 매 쿼리마다 정렬을 하지 않아도 빠르게 데이터를 검색할 수 있도록 지원하게 됩니다.

    DB는 기본적으로 모든 행을 스캔하고 정렬한 뒤 일치하는 행을 필터링하여 결과를 반환합니다.

 

 

index의 단점?

  • 데이터의 수정(insert, update, delete)이 발생할 때마다 연관 index도 업데이트해야 합니다. 즉 다른 쿼리의 성능이 떨어지게 되는 문제점이 있습니다. update의 경우 where 조건에 index를 사용하고 있다면 해당 칼럼을 찾아 변경하는 성능을 높일 수 있습니다. (index update는 동일하다.)
  • Index는 DB 내에서 별도의 저장 공간을 차지합니다.
  • Index를 유지하는 비용이 발생합니다. 대표적으로 데이터 수정 , 삭제, 추가 등의 경우 Index가 업데이트되다가 깨지는 경우가 발생하게 되는데 이때 복구, 정상적인 Index로의 우회 등 추가적인 처리가 필요합니다.

 

 

Index Corruption?

Corrupt Index는 DDL, DML(insert, update, delete) 쿼리를 수행한 뒤 정렬하는 도중이나, Slow Query에 의한 비정상적인 종료에 의해 발생할 수 있습니다. 하지만 확실하게 손상되었는지 확인할 수 없기 때문에, 이를 파악하기 위해서는 테스트를 통해 실행시간과 결과를 관찰하여야 합니다.

 

 

 

index를 사용하여야 하는 시점?

테이블에 인덱스를 추가하는 것은 저장 속도를 어디까지 희생하고, 읽기 속도를 얼마나 더 빠르게 만들어야 하는지의 여부에 따라 결정되어야 합니다.

 

인덱스를 추가하거나 제거할 때마다 성능 테스트를 수행하여 어떤 영향이 미치는지 실질적인 수치를 파악하여야 하고, 

이는 많은 검색 쿼리에서 빈번하게 키로 사용되는 컬럼을 인덱스로 사용한다는 것이 기본 전제가 되는데요, 이를 정량화(수치로 만들어서)하여 판단해야 한는 것이 좋습니다.

 

 

 

index의 알고리즘?

간단하게 Index 알고리즘의 종류와 개념만 작성하였습니다.

  • B-Tree Index : balanced Tree를 사용하는 방식을 의미하며, 일반적으로 사용되는 유형입니다. 칼럼의 값을 변형하지 않고, 원래의 값만을 이용해 Indexing 하는 특성을 가지게 됩니다.

    여러 형태의 변형된 알고리즘을 가집니다. B+-Tree, B-Tree 등*

  • Hash Index : 컬럼의 값을 Hash 값으로 계산하여 Indexing 하는 방식입니다. 매우 빠른 검색을 지원하나 일부 값을 통해 값을 찾는 Pattern matching을 지원하지 못합니다. 주로 메모리 기반의 DB에서 사용됩니다. 

  • Fractal-Tree Index : B-Tree 방식의 단점을 보완하기 위해 고안된 방식입니다. 값을 변경하지 않고 인덱싱 하는 것은 동일하나 데이터가 저장되거나 삭제될 때 발생하는 비용 (Disk I/O)을 줄이도록 설계된 것이 특징입니다. 각 내부 노드에 버퍼를 포함함으로써 데이터를 임시로 저장하고 버퍼가 가득 채워졌을 때 Flush 하게 됨으로 I/O 작업 단위를 크게 만들어서 유지하게 됩니다.

  • R-Tree Index : 2차원 데이터를 Indexing 하고 검색하는 목적을 지니는 방식입니다. 주로 공간 개념 값을 사용하는 GPS나 GIS 서비스에서 사용하게 됩니다.

  • Full Text Search Index : 문서의 내용 전체를 Indexing 하여 특정 키워드가 포함된 문서를 분석, 검색하는 방식에서는 B-Tree 형식을 사용할 수 없기에 사용되는 방식입니다. 크게 Stopword 방식과 N-Gram 방식으로 이야기할 수 있습니다.

 

 

자료 출처


 

 

 

Bedocs Study DbUnit 발표 후기


발표 자료

 

발표하며 느낀 것들

금주 목요일에는 저에게 생소했던 라이브러리인 DbUnit에 대해 학습하고, 정리된 내용과 예제 프로젝트를 이용해 발표하는 시간을 가지게 되었습니다.

 

새로운 개념과 라이브러리를 학습하는 것은 즐겁지만, 어려운 경우도 종종 있었는데, 특히 이번 주제인 DbUnit은 공개된 문서의 초기 설정 등이나 레퍼런스가 부족하여 더 어려움이 있었던 것 같습니다.

 

참여하시는 분들이 대부분 직장인이셔서 야근 등의 문제로 (발표) 일정이 1주일 밀렸었는데, 그 덕에 준비를 좀 더 할 수 있었고 실수(거의?) 없이 라이브로 환경 설정부터 테스트 코드 작성까지 할 수 있었던 것 같습니다.

 

 

뿌듯한 시간이었고 다들 좋은 반응들을 해주셔서 정말 감사했었습니다. 


 

 

 

Go Hello World!


지난 화요일 저녁에 여유가 생겨서 요즘 인프라, 컨테이너 관련 오픈소스나 MSA에서 자주 사용되는 Go 언어를 찾아보고 간단하게 코딩도 해보았습니다.

 

 

학습 중인 자료 (무료 E-Book)

 

중괄호 위치 등의 Coding Convention을 제한하고 메서드 명명 규칙을 기능으로써(?) 제공하는 부분이 정말 재미있었는데요. 이미 기존에 학습하시고 사용하시는 분들이라면 김 빠지실 수 있겠지만, 이런 부분이었습니다.

// private method, 외부 접근 불가능
func sayBye() {
  fmt.Println("bye")
}

// public method, 외부 접근 가능
func SayHello() {
  fmt.Println("hello")
}

소한 즐거움..?

 

현재 자바를 이용해 프로젝트를 하며 드는 생각은 문법이 읽기 좋지만, 너무 많은 코드를 작성하게 된다라는 점이 있는데요. Go는 간단한 문법을 내세우는 언어답게 간결하면서도 제가 좋아하는 정적, 강타입도 지키는 언어이기에 정말 마음에 들었습니다. (변수 타입은 컴파일 시점에서 결정되지만요.)

// Java : 1
public static void main(String[] args) {

        int a = 0, i;
        for (i = 0; i < 10; i++) {
            a += i;
        }

        System.out.println(a);
}

// Java : 2
public static void main(String[] args) {

        int a = 0, i = 0;
        while (i != 10) {
            a += i;
            i++;
        }

        System.out.println(a);
}

// Go
func main() {
        sum, i := 0, 0

    for i < 10; {
        sum += i
        i++ 
    }
    fmt.Println(sum)
}

이런 예제에서는 간결함을 크게 느낄 수는 없겠지만요.

 

물론 없는 문법도 많고.. (예외 처리라던지?) 핵심 라이브러리도 부족하다고 하지만, 모든 언어가 모든 상황에서 좋은 경우는 없기 때문에 (그렇게 믿고 있습니다.) 충분히 감내할 수 있는 부분이라고 생각합니다.

 

여유가 생길 때마다 새로운 기술과 언어를 꾸준하게 공부할 생각인데 이러한 마음가짐이 계속 갔으면 좋겠습니다. ㅎㅎ

 

 

이번 한 주 다들 노고가 많으셨고요. 주말에 푹 쉬시고 다음 주도 힘차게 나아가 봅시다! 파이팅!

 

 

+ Recent posts