Skip to content
isdnetworks
Go back

누가 붙느냐가 구조를 정했다

통계 자료를 여러 곳에서 보게 해 달라는 요청이 왔다. 화면부터 만들려다가 누가 볼 것인지를 먼저 물었다.

Table of contents

Open Table of contents

목표 — 붙는 쪽을 적어 봤다

적어 보니 넷이었다.

1. 운영팀 - 화면에서 본다. 매일
2. 기획팀 - 엑셀로 받아서 가공한다. 주 1회
3. 외부 협력사 - 자기네 시스템에 넣는다. 매일 자동
4. 사내 다른 시스템 - 데이터를 읽어 간다. 실시간

화면 하나로 될 일이 아니었고 각각 필요한 것이 달랐다.

사람이 보는 것과 시스템이 읽어 가는 것은 필요한 모양이 다르다. 만들기 전에 이것을 적어 본 것이 이 일에서 가장 값이 있었고 안 적었으면 화면을 만든 뒤에 다시 만들었을 것이다.

접근 방식을 나눴다

넷을 방식으로 갈랐다.

1. 운영팀     화면
2. 기획팀     화면에서 엑셀 내려받기
3. 협력사     API (인증 필요)
4. 사내 시스템 API 또는 DB 직접 읽기

4번이 걸렸다. MySQL 을 직접 읽게 하면 편하지만 스키마가 바뀔 때 그쪽이 깨진다.

DB 직접 읽기
  + 만들 것이 없다
  - 스키마를 못 바꾼다
  - 누가 무엇을 읽는지 모른다
  - 권한을 세밀하게 못 준다

API 로 했다. 만들 것이 늘지만 나중에 스키마를 바꿀 수 있다. API 를 하나 두는 것이 나중에 바꿀 여지를 남겼다.

외부와 내부의 경계

협력사와 사내 시스템에 같은 API 를 주려다 나눴다.

/api/v1/stats/daily      사내용. 전체 항목
/ext/v1/stats/daily      외부용. 일부 항목만

외부에 나가는 것은 항목을 줄였다. 내부 식별자나 원가 같은 것이 섞여 있었다.

// 외부용에서 빼는 항목
$hidden = ['cost', 'margin', 'internal_code', 'partner_id'];
foreach ($hidden as $k) { unset($row[$k]); }

빼는 목록을 두는 것보다 넣을 목록을 두는 편이 안전했다.

$allowed = ['date', 'order_count', 'amount'];
$out = array_intersect_key($row, array_flip($allowed));

array_intersect_key$allowed 에 있는 것만 남기면 MySQL 에 새 컬럼이 생겨도 기본으로 안 나간다. 뺄 것을 정하는 방식은 새 컬럼이 생길 때마다 그것이 새어 나간다.

인증은 키로 했다.

$key = $_SERVER['HTTP_X_API_KEY'] ?? '';
$partner = $db->row("SELECT * FROM api_partner WHERE api_key = ? AND use_yn='Y'", $key);
if (!$partner) { http_response_code(401); exit; }

키를 파트너별로 나눠서 줬다. 하나를 회수해도 다른 곳은 안 막히고 문제가 생겼을 때 어느 쪽인지 갈린다.

접속 기록도 남겼다.

$db->insert('api_log', [
    'partner_no' => $partner['no'], 'path' => $_SERVER['REQUEST_URI'],
    'ip' => $_SERVER['REMOTE_ADDR'], 'at' => date('Y-m-d H:i:s'),
]);

누가 언제 무엇을 가져갔는지가 남는다. 나중에 받은 값이 이상하다는 연락이 왔을 때 이걸 봤다.

얼마나 자주 부를지도 정했다. 정하지 않으면 1초에 한 번씩 부르는 곳이 나온다.

$count = $db->count("SELECT COUNT(*) FROM api_log WHERE partner_no=? AND at > ?",
                    $partner['no'], date('Y-m-d H:i:s', time()-60));
if ($count > 60) {
    http_response_code(429);
    header('Retry-After: 60');
    exit;
}

분당 60회로 두고 넘으면 429Retry-After 로 언제 다시 오라고 알려 준다. 문서에도 적었는데 적어 두지 않으면 막혔을 때 왜 막혔는지 모른다.

계산은 한 자리에서 했다

넷에게 주는 것이 결국 같은 데이터였다.

class StatService {
    public function daily($from, $to);   // 여기서만 만든다
}

화면도 엑셀도 APIStatService 를 부른다. 계산이 한 자리에 있으니 값이 갈릴 일이 없다.

전에는 화면과 엑셀의 숫자가 달랐던 적이 있었다. 각자 쿼리를 쓰고 있었기 때문이다. 정의가 바뀌어도 한 곳만 고치면 된다.

주소에 판도 넣었다. 나중에 형식을 바꿔야 하면 v2 를 만들고 v1 을 한동안 같이 둔다. 판이 없으면 바꾸는 순간 쓰던 쪽이 다 깨지고 외부에 나가는 것은 우리 마음대로 못 바꾼다.

정리


Share this post on:

Previous Post
조회인 줄 알고 돌렸는데 변경이었다
Next Post
지금 구조로는 필요한 만큼 안 됐다