> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/developer-guide-en/dev-start/integrations/server-side-development-method.md).

# Server-side Development Method

This document covers the server-side development approach.\
It explains the development pattern and directory structure on the server, and describes how to write each class.

***

## Controller, Service, (APIController, Service), Mapper Structure

The server is structured based on the Spring MVC pattern.\
For data processing, it proceeds in a Controller → Service → Mapper structure.\
It is developed in an MSA structure, where the Front (Next.js) calls the API server, which communicates with the DB server.

Below is the program call order

http request → (mapping) → Controller → Service → ServiceImpl → APIController → Service → ServiceImpl → Mapper → XML(query) (BO server) (API server)

***

**Directory Structure**

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2F72tYWTrSSB15AWAEfurn%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202025-12-24%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.46.33.png?alt=media&#x26;token=9ae07f29-bb3a-4d8e-bbe3-4448672e160d" alt=""><figcaption></figcaption></figure>

***

## Writing DTO Classes

* Create dto and entity folders under the relevant business folder in src/main/java, then work within them.
* Pre-define the objects that will be used for data transfer.

**Sample.java**

{% code title="Sample.java" %}

```java
package com.x2bee.api.bo.app.entity;

import javax.validation.constraints.NotNull;
import org.apache.ibatis.type.Alias;
import com.x2bee.common.base.entity.BaseCommonEntity;
import lombok.Getter;
import lombok.Setter;

@Alias("Sample")
@Getter
@Setter
public class Sample extends BaseCommonEntity {
    private static final long serialVersionUID = -5756700830219562201L;
    private Long id;
    @NotNull
    private String name;
    private String description;
}
```

{% endcode %}

***

## Writing the API Controller Class

* Write the Controller for the API server
* Return as a Response object

**SampleController.java**

{% code title="SampleController.java" %}

```java
package com.x2bee.api.bo.app.controller.sample;

import java.util.List;
import javax.validation.Valid;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;
import com.x2bee.api.bo.app.entity.Sample;
import com.x2bee.api.bo.app.service.sample.SampleService;
import com.x2bee.api.bo.base.advice.ApiError;
import com.x2bee.api.bo.base.annotation.IndInfoLog;
import com.x2bee.common.base.exception.AppException;
import com.x2bee.common.base.rest.Response;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/samples")
@Lazy
@Slf4j
@RequiredArgsConstructor
public class SampleController {

    private final SampleService sampleService;

    @GetMapping("")
    public Response<List<SampleResponse>> getAllSamples() {
        return new Response<List<SampleResponse>>().setPayload(sampleService.getAllSamples());
    }

    @GetMapping("/{id}")
    public Response<SampleResponse> getSample(@PathVariable Long id,
            @RequestHeader(value="test-header-key1", required = false) String testHeader1) {
        log.info("id: {}, testHeader1: {}", id, testHeader1);
        return new Response<SampleResponse>().setPayload(sampleService.getSample(id));
    }

    @GetMapping("/search")
    public Response<List<SampleResponse>> searchSamples(SampleRequest sampleRequest) {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.searchSamples(sampleRequest));
    }

    @PostMapping("")
    public Response<String> registerSample(@RequestBody @Valid Sample sample) throws InterruptedException {
        log.info("sampleRequest: {}", sample);
        return new Response<String>();
    }

    @PutMapping("/{id}")
    public Response<String> saveSample(@PathVariable Long id, @RequestBody SampleRequest sampleRequest) {
        log.info("id: {}, sampleRequest: {}", id, sampleRequest);
        return new Response<String>();
    }

    @PatchMapping("/{id}")
    public Response<String> modifySample(@PathVariable Long id, @RequestBody SampleRequest sampleRequest) {
        log.info("id: {}, sampleRequest: {}", id, sampleRequest);
        return new Response<String>();
    }

    @DeleteMapping("/{id}")
    public Response<String> removeSample(@PathVariable Long id) {
        log.info("id: {}", id);
        return new Response<String>();
    }

    @GetMapping("/error")
    public Response<String> getError() {
        if (true) {
            AppException.exception(ApiError.UNKNOWN);
        }
        return new Response<String>();
    }

    /**
     * @deprecated void 유형으로 응답하면 안됩니다. Response<T> 유형으로 응답 해야합니다.
     */
    @PostMapping("/void")
    public void registerVod(@RequestBody SampleRequest sampleRequest) throws InterruptedException {
        log.info("sampleRequest: {}", sampleRequest);
    }

    @PostMapping("/display-samples")
    public Response<String> registerDisplaySample(@RequestBody SampleRequest sampleRequest) throws Exception {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<String>().setPayload(sampleService.registerDisplaySample(sampleRequest));
    }

    @GetMapping("/call-order")
    public Response<List<SampleResponse>> callOrder(SampleRequest sampleRequest) throws Exception {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.callChain(sampleRequest));
    }

    @GetMapping("/infInfoLog")
    @IndInfoLog
    public Response<List<SampleResponse>> infInfoLog(SampleRequest sampleRequest) {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.searchSamples(sampleRequest));
    }

    /**
     * 동영상 업로드 샘플.
     * 상품컨텐츠정보 등록 시 동영상이 업로드 되는 경우의 샘플임.
     * POST formData 로 업로드한다.
     */
    @PostMapping("/mpic")
    public Response<String> registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest) {
        sampleService.registerGoodsContInfoWithMpic(sampleMpicRequest);
        return new Response<String>();
    }
}
```

{% endcode %}

***

## Writing the API Service Class

**SampleService.java**

{% code title="SampleService.java" %}

```java
package com.x2bee.api.bo.app.service.sample;

import java.util.List;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;

public interface SampleService {
    public List<SampleResponse> getAllSamples();
    public SampleResponse getSample(Long id);
    public List<SampleResponse> searchSamples(SampleRequest sampleRequest);
    String registerDisplaySample(SampleRequest sampleRequest) throws Exception;
    public List<SampleResponse> callChain(SampleRequest sampleRequest) throws Exception;
    public void registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest);
}
```

{% endcode %}

Actual implementation

**SampleServiceImpl.java**

{% code title="SampleServiceImpl.java" %}

```java
package com.x2bee.api.bo.app.service.sample;

import java.util.List;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.common.MpicResponse;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;
import com.x2bee.api.bo.app.entity.PrGoodsContInfo;
import com.x2bee.api.bo.app.entity.StMpicMappInfo;
import com.x2bee.api.bo.app.repository.displayrodb.sample.SampleMapper;
import com.x2bee.api.bo.app.repository.displayrwdb.sample.SampleTrxMapper;
import com.x2bee.api.bo.app.service.common.MpicService;
import com.x2bee.api.bo.base.advice.ApiError;
import com.x2bee.common.base.exception.AppException;
import com.x2bee.common.base.rest.Response;
import com.x2bee.common.base.rest.RestApiUtil;
import com.x2bee.common.base.upload.AttacheFileKind;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Lazy
@Slf4j
@RequiredArgsConstructor
public class SampleServiceImpl implements SampleService {

    private final SampleMapper sampleMapper;
    private final SampleTrxMapper sampleTrxMapper;
    private final RestApiUtil restApiUtil;
    private final MpicService mpicService;

    @Value("${app.apiUrl.display}")
    private String displayApiUrl;

    @Value("${app.apiUrl.order}")
    private String orderApiUrl;

    @Value("${sample.prop}")
    private String sampleProp;

    @Override
    public List<SampleResponse> getAllSamples() {
        log.debug("sample prop: {}", sampleProp);
        return sampleMapper.selectAllSamples();
    }

    @Override
    public SampleResponse getSample(Long id) {
        SampleResponse sampleResponse = sampleMapper.selectSampleById(id).orElse(null);
        if (sampleResponse == null) {
            AppException.exception(ApiError.DATA_NOT_FOUND);
        }
        return sampleResponse;
    }

    @Override
    public List<SampleResponse> searchSamples(SampleRequest sampleRequest) {
        return sampleMapper.selectSamples(sampleRequest);
    }

    @Override
    public String registerDisplaySample(SampleRequest sampleRequest) throws Exception {
        return restApiUtil.post(displayApiUrl+ "/api/display/samples", sampleRequest, new ParameterizedTypeReference<Response<String>>() {}).getPayload();
    }

    @Override
    public List<SampleResponse> callChain(SampleRequest sampleRequest) throws Exception {
        return restApiUtil.get(orderApiUrl+ "/api/order/samples/call-display", sampleRequest, new ParameterizedTypeReference<Response<List<SampleResponse>>>() {}).getPayload();
    }

    /**
     * 동영상 업로드 샘플 서비스
     */
    @Override
    public void registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest) {
        // 파일 업로드
        MpicResponse mpicResponse = mpicService.uploadMpic(sampleMpicRequest.getFile1(), sampleMpicRequest.getDirection(), AttacheFileKind.GOODS);

        // 업무컨텐츠정보 등록
        PrGoodsContInfo prGoodsContInfo = insertPrGoodsContInfo(sampleMpicRequest, mpicResponse);

        // 동영상변환상태정보 등록
        registerMpicMappInfo(prGoodsContInfo, mpicResponse);
    }

    // 업무컨텐츠정보 등록 - 업무별 비지니스 로직 구현필요
    private PrGoodsContInfo insertPrGoodsContInfo(SampleMpicRequest sampleMpicRequest, MpicResponse mpicResponse) {
        PrGoodsContInfo prGoodsContInfo = new PrGoodsContInfo();
        prGoodsContInfo.setGoodsNo("TEST_001");
        prGoodsContInfo.setCmtTypCd("02");
        prGoodsContInfo.setCmtSerialNo("10"+RandomStringUtils.randomNumeric(6));
        prGoodsContInfo.setOptnCatNo("1000");
        prGoodsContInfo.setOptnNo("BLACK");
        prGoodsContInfo.setImgGbCd("T01");
        prGoodsContInfo.setBaseImgYn("N");
        prGoodsContInfo.setContFilePathNm(null);
        prGoodsContInfo.setContFileNm(mpicResponse.getFileNm());
        prGoodsContInfo.setTrnfTextCont("testTrnfTextCont");
        prGoodsContInfo.setSysRegId("FRONT");
        prGoodsContInfo.setSysModId("FRONT");
        sampleTrxMapper.insertPrGoodsContInfo(prGoodsContInfo);
        return prGoodsContInfo;
    }

    // 동영상변환상태정보 등록
    private void registerMpicMappInfo(PrGoodsContInfo prGoodsContInfo, MpicResponse mpicResponse) {
        StMpicMappInfo stMpicMappInfo = new StMpicMappInfo();

        // 원본경로명이 "/" 로 시작하도록 처리함.
        String s3OrigPathNm = StringUtils.startsWith(mpicResponse.getS3OrigPathNm(), "/") ? mpicResponse.getS3OrigPathNm() : "/" + mpicResponse.getS3OrigPathNm();
        stMpicMappInfo.setOrgPathNm(s3OrigPathNm);
        stMpicMappInfo.setTblNm("pr_goods_cont_info");
        stMpicMappInfo.setRef1Val(prGoodsContInfo.getGoodsNo());
        stMpicMappInfo.setRef2Val(prGoodsContInfo.getCmtTypCd());
        stMpicMappInfo.setRef3Val(prGoodsContInfo.getCmtSerialNo());
        stMpicMappInfo.setSysRegId("SAMPLE");
        stMpicMappInfo.setSysModId("SAMPLE");

        mpicService.registerMpicMappInfo(stMpicMappInfo);
    }
}
```

{% endcode %}

***

## Writing the Mapper Class and Queries

* Create a repository/{db connection name} folder under the relevant business folder in src/main/java, then write the Mapper interface

**SampleMapper.java**

{% code title="SampleMapper.java" %}

```java
package com.x2bee.api.bo.app.repository.displayrodb.sample;

import java.util.List;
import java.util.Optional;

import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;

public interface SampleMapper {
    public List<SampleResponse> selectAllSamples();
    public Optional<SampleResponse> selectSampleById(Long id);
    public List<SampleResponse> selectSamples(SampleRequest request);
}
```

{% endcode %}

* Write the corresponding query (XML) under src/main/resources
* Specify the above Mapper class in the namespace
* Specify the data object in resultType or parameterType

**SampleMapper.xml**

{% code title="SampleMapper.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.x2bee.api.bo.app.repository.displayrodb.sample.SampleMapper">

  <sql id="sampleList">
    SELECT 1 as id, 'name1' as name, 'desc1' as description
    union all
    SELECT 2 as id, 'name2' as name, 'desc2' as description
    union all
    SELECT 3 as id, 'name3' as name, 'desc3' as description
  </sql>

  <!-- 전체 샘플 조회 -->
  <select id="selectAllSamples" resultType="sampleResponse">
    /* SampleMapper.selectAllSamples */
    <include refid="sampleList" />
  </select>

  <!-- 샘플 단건 조회 -->
  <select id="selectSampleById" parameterType="long" resultType="sampleResponse">
    /* SampleMapper.selectSampleById */
    select * from (
      <include refid="sampleList" />
    ) a where id = #{id}
  </select>

  <!-- 샘플 목록 조회 -->
  <select id="selectSamples" parameterType="sampleRequest" resultType="sampleResponse">
    /* SampleMapper.selectSamples */
    select * from (
      <include refid="sampleList" />
    ) a
    <where>
      <if test="id != null">
        and id = #{id}
      </if>
      <if test="name != null and name != ''">
        and name = #{name}
      </if>
      <if test="description != null and description != ''">
        and description = #{description}
      </if>
    </where>
  </select>

</mapper>
```

{% endcode %}

***

**When proceeding with the above work, the final package structure is as follows.**

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FQxigY1UGlvbCAVTNbaxR%2Fimage-20220328-050404.png?alt=media&#x26;token=06c5cd5e-6fe0-427a-838a-c9d371daaf72" alt=""><figcaption></figcaption></figure>
