본문 바로가기

카테고리 없음

REST, RESTful API 구현

REST(REpresentational State Transfer) 이란?

웹 기반의 소프트웨어 아키텍처 스타일 중 하나이다.

소프트웨어 아키텍처 스타일이란 각 스타일에 정의된 패턴 또는 설계 접근방식을 나타낸 것이다.

각각의 데이터를 자원으로 보고, 각 자원을 고유한 식별자-URI-를 부여해 자원을 정의하고 주소를 지정, 활용하는 패턴을 가지고 있다.

 

REST 설계 원칙

• 클라이언트-서버는 독립적이어야 하고, 이는 시스템의 확장과 재사용성을 향상시킨다.
• 무상태성을 유지하며, 서버는 클라이언트의 상태를 관리하지 않음으로써 서버의 확장, 가용성을 향상시킨다.
• 서버 응답은 캐싱하여 반복되는 동일한 요청에 네트워크 지연을 감소시키고 성능을 향상시킨다.
• 시스템은 여러 계층으로 구성되어 각 계층을 통해 클라이언트가 서버와 직접적인 연결 여부를 알 수 없게한다.
  이는 시스템의 확장성과 보안성을 향상시킨다.
• 서버의 인터페이스는 일관성있게 설계되어야 한다. HTTP 메소드, 응답 표현의 형식, 에러 처리, 리소스 식별 등을    일관성 있게 설계함으로써 유지보수성을 향상시키고 클라이언트측과 상호작용을 통해 생산성을 향상 시킨다.

설계 원칙을 준수하여 웹 서비스의 개발과 통합을 단순화하고 확장성, 성능 향상에 도움을 주는 방법이다.

 


API(Application Programing Interface) 이란?

소프트웨어 어플리케이션끼리 상호 작용하기 위한 인터페이스이다. 일반적으로 웹 API의 경우 네트워크를 통해 클라이언트와 서버가 통신할때 사용된다. 클라이언트에서 서버로 데이터를 요청하거나 서버에서 데이터를 전송할 수 있는 인터페이스를 제공한다. 일반적으로 웹기반 소프트 아키텍처 스타일인 REST를 적용하며 아키텍처 원칙을 준수한 API를 RESTful API로 의미한다.

 


RESTful API 란?

REST(REpresentational State Transfer) 소프트웨어 아키텍처 스타일을 준수하여 설계된 API(Application Programing Interface)다. 웹 기반 분산 시스템에서 주로 사용되며 클라이언트와 서버의 통신을 원할하게 처리하기 위한 인터페이스다.

 

특징

• 리소스(데이터) 중심 설계로 각 리소스는 고유 식별자-URI-를 가지고 있어 리소스를 통해 접근 조작할 수 있다.
• HTTP 메소드를 다양한 작업에 따라 일관성 있게 작성해야 한다. ex) GET - 조회, POST - 생성, DELETE - 삭제 등
• Stateless(무상태)를 유지하여 서버와 클라이언트를 분리하여 확장성과 독립성을 향상 시킨다.
• 응답에 HTTP 상태와 정보를 포함시켜 클라이언트는 응답에 대한 정보를 이해하고 활용할 수 있다.
• 하이퍼 링크를 통해 다른 리소스를 포함하여 하나의 작업에 여러개의 리소스를 포함할 수 있다. 이는 API의 유연    성과 확장성을 향상시킨다.

장점

• 간결하고 직관적인 인터페이스를 제공하여 이해가 쉽고 사용성이 용이하다.
• 클라이언트와의 의존성이 낮아 다양한 클라이언트에 적용할 수 있다.
• HTTP의 기본 기능을 사용하여 캐싱, 보안 등의 이점이 있다.

단점

• 일부 복잡한 작업을 HTTP 메소드로 표현하기에 제한적일 수 있다. 상태변화가 복잡하거나 트랜잭션 처리가 필요    한 경우
• 과도한 답 메시지와 하이퍼링크를 사용할 경우 네트워크 오버헤드가 발생 할 수 있다.
• HTTP를 기반으로 작동하기 때문에 인증, 인가, 암호화와 같은 보안에 대한 조치가 필요 하다.

 


Resource, Method 그리고 URI

리소스(Resource), 메소드(Method), URI 설계는 RESTful API 개발에서 중요한 부분이다. 이 구성요소들을 일관적이고 명확하게 설계해야 RESTful 한 API를 구현했다 할 수 있다.

리소스(Resource)

리소스는 클라이언트가 액세스하고 조작하는 개념적 또는 물리적 개체를 나타낸다. 때문에 일반적으로 명사로 표현되며 API의 엔드포인트로 매핑된다. 예시로는 사용자, 게시물, 주문 개체가 각각 리소스로 간주될 수 있다.

메소드(Method)

메소드(Method)은 클라이언트가 서버의 리소스와 상호작용하기 위해 수행하는 작업을 나타냅니다. HTTP 프로토콜에서 주로 사용되는 메소드는 다음과 같다.
GET : 리소스 조회, 검색 요청 /
POST : 새로운 리소스 생성, 리소스에 대한 작업 요청 / 요청 본문을 통해 데이터를 전달한다.
PUT : 리소스의 전체적인 업데이트 요청 / 요청 본문을 통해 데이터를 전달한다.
PATCH : 리소스의 부분적인 업데이트 요청 / 
DELETE : 리소스 삭제 요청
URL로 데이터를 전달 : GET, DELETE
요청 본문으로 데이터를 전달 : POST, PUT, PATCH

메소드는 리소스에 수행할 작업에 따라 적절하게 작성해야 한다. 일관성 있는 메소드 설계를 통해 클라이언트는 어떤 작업을 수행할 수 있는지 예측하고 활용할 수 있다.

<form method="POST" action="/post/1">
	<input type="hidden" name="_method" value="PUT">
</form>

HTML에서 지원하는 기본적으로 GET, POST만을 지원하기 때문에  PUT, PATCH, DELETE 와 같은 메소드는 메소드 스푸핑(Method Spoofing)을 이용해 사용한다.

 

URI 설계

URI(Uniform Resource Identifier)는 리소스를 식별하고 위치를 나타내는 문자열이다. URI는 리소스를 포함하여 리소스에게 접근하기 위한 경로까지를 나타낸다. 일관성과 가독성을 가지도록 설계해야 하며 일반적으로 명사형으로 표현되고 계층 구조를 반영할 수 있도록 한다.

URI 작성 규칙
명확하고 직관적인 구조 : 명사로 시작하여 계층 구조를 반영하고 경로를 구성한다.
ex) http://test.com/users/{id}/post

소문자 사용 : 소문자로 작성하여 일관성율 유지한다.

하이픈 사용 : 단어와 단어를 구분하기 위해 하이픈을 사용하여 가독성을 높인다.
ex) http://test.com/user-profile

명사 사용 : 리소스를 대표하는 명사를 사용하여 가독성과 일관성을 높인다.

복수형 사용 : 리소스가 복수 개체인 경우 복수형을 사용하여 직관성을 높인다.

특수문자 처리 : URI는 URL 인코딩을 사용하여 특수문자 처리를 한다. 공백은 "%20", 슬래시는 "%2F"​

 

이와 같은 규칙을 적용하여 가독성과 일관성을 높이고 사용자가 URI를 쉽게 이해하고 사용하도록 작성해야 한다.

설계 예시
단일 리소스 조회:
GET /users/{id}: 특정 사용자의 정보 조회

다중 리소스 조회:
GET /users: 모든 사용자의 정보 조회

리소스 생성:
POST /users: 새로운 사용자 생성

리소스 수정:
PUT /users/{id}: 특정 사용자의 정보 전체 업데이트

리소스 삭제:
DELETE /users/{id}: 특정 사용자 삭제

관계된 리소스:
GET /users/{id}/posts: 특정 사용자가 작성한 모든 게시물 조회

이와 같이 파라미터 유무에 따라 단일 또는 다중을 표현하고 계층 구조를 반영하여 작업에 대한 예측을 가능하게 하는게 좋은 URI 설계라할 수 있다.


SpringBoot를 사용한 RESTful API 구현

환경 설정

Framework : SpringBoot 2.7.12  |  JDK : JAVA 11  |  IDE : IntelliJ  |  DB : OracleDB  |  build Tool : Gradle
Dependency :
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.oracle.database.jdbc:ojdbc8'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
	implementation group: 'com.auth0', name: 'java-jwt', version: '4.0.0'
#Application.properties

spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=C##APITEST
spring.datasource.password=APITEST
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

mybatis.mapper-locations=mapper/*.xml
mybatis.type-aliases-package=com.newbie.training
mybatis.configuration.map-underscore-to-camel-case=true

 

DB Table 스키마

DB 데이터 

구현 코드

User

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String userId;

    private String userPw;

}

HttpStatusMsg

public class HttpStatusMsg {

	public static final String  HTTP_OK =  "{code: 200, message: 요청 성공}";
    
	public static final String HTTP_UNAUTHORIZED = "{\"code\": \"401\", \"message\": \"인증되지 않음, 인증 후 재시도 바랍니다.\"}";

	public static final String HTTP_NOT_FOUND = "{\"code\": \"404\", \"message\": \"요청한 리소스를 찾을 수 없음, 확인 후 다시 요청\"}";

}

UserController

public class UserController {
    private final UserService userService;
    
	public UserController(UserService userService) {
		this.userService = userService;
	}

	@GetMapping("/{userId}")
	public ResponseEntity<?> getUserById(@PathVariable String userId) {
        	User user = userService.getUserById(userId);
        	SuccessResponseBody responseBody;
                if (user != null) {
                    return ResponseEntity.ok(new SuccessResponseBody(HttpStatusMsg.HTTP_OK, user));
                } else {
                    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(HttpStatusMsg.HTTP_NOT_FOUND);
                }
    	}
   }

UserService

    @Override
    public User getUserById(String userId) {
        return userRepository.getUserById(userId);
    }

UserRepository

    @Override
    public User getUserById(String userId) {
        return session.selectOne("UserMapper.selectUserById",userId);
    }

UserMapper

<mapper namespace="UserMapper">

	<select id="selectUserById" resultType="User">
        	SELECT * FROM USER_TBL WHERE USER_ID = #{userId}
    	</select>
    
</mapper>

 

GET /{userId}
GET 메소드를 통해 userId를 파라미터로 사용하여 유저의 정보를 조회하는 간단한 예제이다. URL을@PathVariable로 파라미터로 사용하며 Service > Repository를 통해 DB에서 USER의 정보를 가지고 오는
간단한 예제이다. 

 

실행 결과

GET /userA 의 결과 값
정상적으로 출력이 된 것을 확인 했다.

GET /userB 의 결과 값
일치하는 데이터가 없기 때문에 404 Not Found 와 함께 에러 문구가 출력되었다.