# 다보리 - 백엔드 Quick Start

# 백엔드 내부 흐름도

### 다보리 백엔드에서는 프론트엔드가 요청을 하게되면 

Router -&gt; Middleware -&gt; Controller -&gt; Handler(Controller도 Model도 되는 애매모호한 함수들이 서로 호출하며 결과값 도출) -&gt; Model 순으로 가며 Controller에서 데이터베이스의 값들을 Json형식으로 변환 후 프론트엔드로 보낸다.

# 백엔드 main_api 설치해보기

### **MAIN\_API 설치 방법!**

#####  1. WSL을 설치합니다.

#####  2. 아이디와 비밀번호를 지정하여 서버에 접속합니다. 

<div class="contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950" id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0%EC%98%88%29-sudo-add"><div class="overflow-y-auto p-4" dir="ltr"> `예) sudo adduser <username>. `</div></div>#####  3. home 경로에 새로운 백엔드를 설치할 디렉토리를 생성합니다.

 `<span class="hljs-built_in">cd</span> /home`

 `<span class="hljs-built_in">mkdir</span> main_api`

#####  4. 생성이 됐으면 main\_api 폴더로 들어갑니다. (`<span class="hljs-built_in">cd</span> main_api)`  


#####  5. wget [http://dbr02-wget.daboryhost.com/main\_api.tar.gz](http://dbr02-wget.daboryhost.com/main_api.tar.gz) 를 입력

#####  (wget은 웹페이지에서 파일을 다운로드하기 위한 도구)

**설치가 완료되었으면 아래 디렉토리들이 존재하는지 확인해주세요.**

**(1). cache-key-pair**

**(2). weberp-go**

**(3). weberp-queries**

**(4). mybin**

#####  6. 파일들이 전부 존재하면 디렉토리 접근 후 실행파일 가동하여 서버 시작합니다

 `<span class="hljs-built_in">1. cd</span> weberp-go`

 `2. ./weberp-go`

# MAIN_API와 프론트엔드 연결하기

Docker(프론트엔드)와 로컬(백엔드)에 각각 띄웠다는 가정하에 설명을 시작하겠습니다

<div id="bkmrk-.env.dabory-main_api"><div>.env.dabory</div><div>MAIN_API_URL='http://host.docker.internal:18080' &lt;- URL만 수정해주면 됩니다.</div></div>#### MAIN\_API\_URL = host.docker.internal:(포트)

**프론트엔드 애플리케이션이 Docker 컨테이너에서 실행되고, 백엔드 서버는 로컬(호스트 머신)에서 실행될 때**

<div id="bkmrk-docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%82%B4%EB%B6%80%EC%97%90%EC%84%9C-loc">- Docker 컨테이너 내부에서 `localhost`를 사용하면 컨테이너 **자기 자신**을 참조하게 됩니다.
- 프론트엔드가 Docker 컨테이너에서 실행되고, 백엔드 서버는 로컬(호스트 머신)에서 실행될 때.

</div>#### MAIN\_API\_URL= localhost:(포트)

Docker를 사용하지 않는 순수 로컬 개발 환경이라면 이렇게 사용

<div id="bkmrk-"></div><div id="bkmrk--1">  
</div>##### **ThunderClient로 통신 테스트**

<div id="bkmrk-url-%3D-http%3A%2F%2Flocalho">url = http://localhost:18080/gate-token-get </div><div id="bkmrk-body%EC%97%90-%ED%82%A4-%EA%B0%92-%EB%84%A3%EA%B3%A0-%28client">body에 키 값 넣고 (ClientId, Base64) 게이트 토큰 받기 -&gt; 게이트토큰 발급 후 헤더에 넣어서 엔드포인트 요청 -&gt; 요청이 돌아오면 성공</div><div id="bkmrk--2"></div><div id="bkmrk--3">  
</div>

# Golang 변경사항 *



# 개선된 Limit 사용 방식

#### **1. 기존 방식 : Limit와 IsntPagination**

기존의 백엔드 설계 방식에서는 **모든 데이터를 가져오기 위해** 다음과 같이 설계되었습니다

- `QueryVars.IsntPagination = true`를 설정하여 페이지네이션을 비활성화
- `vRet.PageVars.Limit` 값을 매우 큰 숫자(예: `1000000000`)로 설정

하지만 이러한 접근 방식은 다음과 같은 문제가 있었습니다

- **코드 가독성 저하**
- **불필요한 메모리 낭비**: 너무 큰 `Limit` 값은 메모리와 리소스의 낭비를 발생 시킬 수 있습니다.

#### **2. 개선된 방식**

**`IsntPagination`** ListType1 파라미터 옵션을 제거하고, `Limit = 0`으로 설정하면 **모든 데이터를 한 번에 가져오도록** 설계되었습니다.

 **(Limit &gt; 500 이상이면 0으로 처리됩니다)**

##### **개선 내용 :**

- **코드 단순화**: 불필요한 `IsntPagination` ListType1 파라미터 옵션을 제거하여 설정을 간소화.
- **자원 최적화**: `Limit = 0`으로 동작을 제어하므로 메모리 사용이 최적화됨.
- **유지보수 용이**: 더 직관적인 코드 구조로 인해 개발자가 쉽게 이해하고 수정 가능.

**<span class="hljs-number">  
</span>**

#### **3. Query File에서의 Limit 사용**

페이징 처리가 필요한 경우만 쿼리 파일에 `LIMIT`와 `OFFSET`을 명시적으로 설정하면 됩니다. (페이징 처리가 불필요할 경우 안넣어줘도 됩니다)

SELECT  
 mx.id as id,  
 turbo\_thumb as c1,  
 item\_name as c2,  
 item\_slug as c3  
FROM dbr\_item as mx  
 INNER JOIN dbr\_igroup as mb ON mx.igroup\_id = mb.id  
\-- @where  
limit 1

`LIMIT`은 반환해야 하는 데이터의 **시작 위치(`OFFSET`)와 개수**를 계산하기 때문에 추가적인 연산 작업이 발생하기 때문에 쿼리 속도가 늦어집니다.

# DB에서 ListToken 활용

### **ListToken 필드란?**

- `DbtListType1` 테이블에는 `<strong>ListToken</strong>` **필드**가 포함되어 있음
- 특정 회원을 필터링하여 조회할 때, 해당 사용자들의 데이터가 `DbtListType1` 테이블에 저장됨
- **조회된 모든 사용자들은 동일한** `<strong>ListToken</strong>` **값을 부여받음**

### **ListToken의 동작 방식**

1. **새로운 필터로 조회할 때마다** 기존의 `DbtListType1`의 데이터가 **삭제 후 재삽입됨**
2. 새로운 데이터가 삽입될 때 **새로운** `<strong>ListToken</strong>` **값이 생성됨**
3. 이를 통해 **사용자가 필터한 회원 데이터로 다양한 작업(Pick, Act, Page 등)을 수행 가능**

### **활용 예시**

<table id="bkmrk-%EB%8F%99%EC%9E%91-%EC%84%A4%EB%AA%85-%ED%9A%8C%EC%9B%90-%EB%AA%A9%EB%A1%9D-%ED%95%84%ED%84%B0%EB%A7%81-%ED%8A%B9%EC%A0%95-%EC%A1%B0" style="width: 80.4938%;"><tbody><tr><th style="width: 24.1439%;">동작</th><th style="width: 75.8585%;">설명</th></tr><tr><td style="width: 24.1439%;">**회원 목록 필터링**</td><td style="width: 75.8585%;">특정 조건으로 필터링한 회원 목록을 `DbtListType1`에 저장</td></tr><tr><td style="width: 24.1439%;">**ListToken 활용**</td><td style="width: 75.8585%;">동일한 `ListToken`을 통해 특정 필터링된 회원 집합을 유지</td></tr><tr><td style="width: 24.1439%;">**데이터 재사용**</td><td style="width: 75.8585%;">한 번 조회된 데이터를 `Pick`, `Act`, `Page` 등의 작업에 활용</td></tr></tbody></table>

**이 방식으로 회원 데이터 조회의 유연성을 높이며, 필터링된 결과를 효율적으로 활용할 수 있도록 설계됨**

# 대용량 데이터 처리를 위한 기술 활용 (Kafka, ES)

## 대용량 데이터 처리 및 Kafka + Bulk 활용

### **데이터 처리 흐름**

1. **DB -&gt; Kafka -&gt; ElasticSearch** 순서로 데이터가 전달됨
2. **Kafka를 bucket 단위로 나누어 데이터 전송**하여 효율적인 처리
3. **ElasticSearch에는 Bulk API를 활용하여 대량의 데이터를 일괄 삽입**

### **주요 개선 사항**

1. **Kafka를 Bucket 단위로 나눠서 전송** → 데이터 스트리밍 성능 최적화
2. **ElasticSearch에 Bulk 처리 적용** → 인덱싱 성능 향상 및 네트워크 부하 감소
3. **대용량 데이터(150,000개 이상)를 효율적으로 처리**하여 빠른 데이터 동기화 가능

### **Kafka Bucket 처리 예시**

```go
qryCnt := models.QryCount(y.Db, cntStr)
	fmt.Println("qryCnt:", qryCnt)

	bucketCnt := 0
	if qryCnt == 0 {
		bucketCnt = 0
	} else {
		bucketCnt = 1 + (int(qryCnt) / p.Limit)
	}

    fmt.Println("bucketCnt:", bucketCnt)
	for i := 0; i < bucketCnt; i++ {
		// for i := 0; i < 3; i++ {
		p.Offset = p.Limit * i
		sqlStr, _, _ := models_join.ListType1PlainQryStrGet(y, lt, p, q, "/list")
		if err := t.ProduceaBucketToKafka(y, "kkk", headers, sqlStr, topic); err != nil {
			return errors.New(e.PageQryErr("vrt452", ": "+err.Error()))
		}
	}
```

👉 **Bucket을 활용하여 병렬 처리 가능!**

### **ElasticSearch Bulk API 예시**

```go
	for _, row := range v.Page {
		row.ClientCode = clientCode
		fmt.Printf("BuyerId: %d, LastSorderDate: %s, ClientCode : %s\n", row.Id, row.LastSorderDate, row.ClientCode)
		row.LastVistDate = row.LastSorderDate
		// Meta 데이터 작성
		meta := fmt.Sprintf(`{ "index": { "_index": "%s", "_id": "%d" } }%s`, esIndex, row.Id, "\n")
		bulkRequest.WriteString(meta)

		// 문서 데이터 작성
		data, _ := json.Marshal(row)
		bulkRequest.WriteString(string(data) + "\n")
		fmt.Println(string(data)) // 데이터를 출력하여 검증

		fmt.Printf("Prepared document for BuyerId: %d\n", row.Id)
	}

	// Bulk 요청 전송
	res, err := es.Bulk(bytes.NewReader(bulkRequest.Bytes()))
	if err != nil {
		return fmt.Errorf("bulk request failed: %w", err)
	}
	defer res.Body.Close()

	// Elasticsearch 응답 확인
	if res.IsError() {
		log.Printf("Bulk indexing failed: %s", res.String())
		return fmt.Errorf("bulk indexing failed")
	}
```

👉 **Bulk API를 활용하면 한 번에 많은 데이터를 삽입 가능!**

✅ **이 방식은 대량의 데이터를 효율적으로 처리하고 성능을 최적화할 수 있도록 설계됨.**

# Echo에서 HTTP 응답 반환

## 1. 개요

Dabory의 **Go 언어에서는 Echo 프레임워크로 프론트엔드 단에 응답데이터로 Json 데이터를 넘겨줘야 하기때문에** `c.String()`과 `c.JSONBlob()`을 사용하여 HTTP 응답을 Json으로 처리하는 방법을 설명한다.

---

#### 2. **`c.String(statusCode int, response string)` 사용법**

### ✅ 개요

- `c.String()`은 **단순 문자열 응답을 반환**하는 함수이다.
- `statusCode`는 HTTP 상태 코드이며, `response`는 프론트엔드로 보낼 응답 본문이다.

### ✅ 사용 예제

<div class="contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950" id="bkmrk-return-c.string%28607%2C"><div class="overflow-y-auto p-4" dir="ltr">var c echo.Context</div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">return</span> c.String(<span class="hljs-number">607</span>, <span class="hljs-string">""</span>)`</div></div>### ✅ 동작 방식

- `607`이라는 **커스텀 HTTP 상태 코드**를 응답으로 보낸다.
- 응답 본문은 `""` (빈 문자열)이다.
- **프론트엔드에서 `response.status` 값이 `607`인지 확인 가능**.

---

#### 3. `c.JSONBlob(statusCode int, jsonData []byte)` 사용법

### ✅ 개요

- `c.JSONBlob()`은 **JSON 형식의 응답을 반환**하는 함수이다.
- `statusCode`는 HTTP 상태 코드이며, `jsonData`는 이미 변환된 JSON 데이터(`[]byte`)이다.

### ✅ 사용 예제

<div class="contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950" id="bkmrk-ret%2C-err-%3A%3D-json.mar"><div class="overflow-y-auto p-4" dir="ltr">`ret, err := json.Marshal(vRet)<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {    <span class="hljs-keyword">return</span> c.String(<span class="hljs-number">500</span>, <span class="hljs-string">"Failed to encode JSON: "</span>+err.Error())}<span class="hljs-keyword">return</span> c.JSONBlob(http.StatusOK, ret)`</div></div>### ✅ 동작 방식

1. `json.Marshal(vRet)`을 사용하여 `ZbaksanSorderEyetestRes` 구조체를 JSON 데이터로 변환한다.
2. 변환된 JSON을 `c.JSONBlob(http.StatusOK, ret)`을 통해 프론트엔드로 전송한다.
3. **프론트엔드는 `response.json()` 또는 `response.data`로 JSON을 받을 수 있다.**

---

## 4. `c.String()` vs `c.JSONBlob()` 비교

<table id="bkmrk-%EA%B8%B0%EB%8A%A5-c.string%28%29-c.json"><thead><tr><th>기능</th><th>`c.String()`</th><th>`c.JSONBlob()`</th></tr></thead><tbody><tr><td>응답 형식</td><td>**문자열(String)**</td><td>**JSON (바이트 배열)**</td></tr><tr><td>사용 목적</td><td>단순 메시지, 상태 코드 반환</td><td>JSON 데이터 응답</td></tr><tr><td>예제 코드</td><td>`c.String(607, "")`</td><td>`c.JSONBlob(http.StatusOK, ret)`</td></tr><tr><td>프론트에서 처리</td><td>`response.text()`</td><td>`response.json()` 또는 `response.data`</td></tr></tbody></table>

---

## 5. 요약

✅ **`c.String(607, "")`**

- HTTP 상태 코드 `607`을 반환하여 프론트엔드에서 감지 가능.
- 프론트엔드에서 `response.status` 값으로 확인 후 특정 로직 실행.

✅ **`c.JSONBlob(http.StatusOK, ret)`**

- JSON 데이터를 반환할 때 사용.
- `json.Marshal()`을 통해 JSON 변환 후 프론트엔드에서 `response.json()`으로 처리.