프랑스 주소 검증 API 구축, 2600만 개 주소

발행: (2026년 3월 7일 PM 09:20 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

Source:

문제: 단편화된 프랑스 지리 데이터

프랑스에서 핀테크 제품을 구축한다면 KYC 준수를 위해 고객 주소를 검증해야 합니다. 간단해 보이죠?

현재 상황 (2026)

API / 서비스비용SLA요청 제한회사 데이터
API Adresse (BAN)무료없음50 req/s아니오
La Poste RNVP아니오 (공개 REST API 없음)
Google Address Validation$0.005 /요청아니오 (SIRENE 통합 없음)
INSEE API SIRENE무료주소 검증 기능 없음, ~500 ms 지연, 별도 인증 필요

적절한 KYC를 수행하려면 일반적으로 두 개 이상의 이러한 API가 필요하며, 각각 고유한 인증 방식, 응답 형식 및 요청 제한 제약이 있습니다.
우리는 모든 기능을 하나의 API로 구현하기로 결정했습니다.

Architecture Overview

GEOREFER는 간단한 Java 스택을 기반으로 구축되었습니다:

Java 11 + Spring Boot 2.7.5
PostgreSQL 16 (42 M+ rows across 12 tables)
Redis 7 (API‑key cache, TTL 5 min)
Elasticsearch 7.17 (city autocomplete, fuzzy search)

Layered design

REST Controllers (17 controllers, 39 endpoints)

Business Services (12 interfaces, 16 implementations)

Repositories (JPA + Elasticsearch)

PostgreSQL + Redis + Elasticsearch

BAN에서 2,600만 개 주소 가져오기

BAN은 데이터를 CSV 파일 형태로 매월 업데이트하여 제공합니다. 전체 데이터셋은 압축 시 약 3.5 GB입니다.

가져오기 전략

  1. 다운로드 최신 BAN CSV 내보내기 파일.
  2. 스트리밍 CSV 리더로 파싱 (전체 파일을 메모리에 올리지 않음).
  3. JDBC 배치 작업을 이용해 배치 삽입 (batch size = 5000).
  4. Elasticsearch에 도시 데이터를 색인하여 자동완성 기능 제공.

프랑스 행정 계층

Region (18) → Department (101) → Commune (35 000+) → Address (26 M)

파리, 리옹, 마르세유는 자체 INSEE 코드를 가진 하위 행정구역인 arrondissement(구역)를 가지고 있습니다.

우리는 french_town_desc 테이블에 전체 계층 구조를 저장합니다:

SELECT f.name,
       f.insee_code,
       f.postal_code,
       d.name AS department,
       r.name AS region
FROM   georefer.french_town_desc f
JOIN   georefer.department d ON f.department_code = d.code
JOIN   georefer.region r ON d.region_code = r.code
WHERE  f.name ILIKE 'paris%';

주소 검증 및 GeoTrust 점수

엔드포인트: POST /addresses/validate
입력: 프랑스 주소.
출력:

  • 신뢰도 점수 (0‑100) – 주소가 존재한다는 확신 정도.
  • GeoTrust 점수 (0‑100) – KYC를 위한 복합 신뢰도 점수.
  • 검증된 주소 – 정규화, 교정, GPS 좌표 포함.
  • AFNOR 형식 – 우편 표준 NF Z 10‑011 포맷.

점수 모델

구성 요소가중치측정 항목
신뢰도35 %거리 수준 주소 매칭
지리 일관성25 %우편 번호 ↔ 시/군 ↔ 부서 교차 검증
우편 일치20 %우편 번호 정확도 (정확, 부분, 무효)
국가 위험20 %FATF/GAFI 국가 위험 등급

예시 요청

curl -X POST 'https://georefer.io/geographical_repository/v1/addresses/validate' \
  -H 'Content-Type: application/json' \
  -H 'X-Georefer-API-Key: YOUR_API_KEY' \
  -d '{
        "street_line": "15 Rue de la Paix",
        "postal_code": "75002",
        "city": "Paris",
        "country_code": "FR"
      }'

예시 응답

{
  "success": true,
  "data": {
    "validated_address": {
      "street_line": "15 Rue de la Paix",
      "postal_code": "75002",
      "city": "PARIS",
      "country": "France"
    },
    "confidence_score": 95,
    "geotrust_score": {
      "overall": 92,
      "level": "LOW",
      "components": {
        "confidence": 95,
        "geo_consistency": 100,
        "postal_match": 100,
        "country_risk": 0
      }
    }
  }
}

Elasticsearch for City Autocomplete

City autocomplete must cover 35 000 communes.

  • Fuzzy search: GET /cities/search?q=Monplier (using fuzziness: AUTO) correctly returns Montpellier.

다중 테넌트 API 키 및 속도 제한

GEOREFER는 5개의 구독 플랜을 제공하는 SaaS입니다:

플랜일일 한도분당 속도가격
DEMO5010Free
FREE10010Free
STARTER5 0003049 EUR/mo
PRO50 00060199 EUR/mo
ENTERPRISEUnlimited200Custom

각 API 키는 속도 제한을 위해 자체 토큰 버킷(Bucket4j)을 갖습니다. 인증은 Spring 필터 체인을 통해 흐릅니다:

Request
   → API‑key validation (Redis cache)
   → Quota check
   → Rate‑limit enforcement
   → Feature‑gate evaluation
   → Controller

Feature Gate는 주어진 플랜에 대해 어떤 엔드포인트와 점수 산정 구성 요소를 사용할 수 있는지 결정합니다. 이를 통해 상위 티어 고객은 전체 GeoTrust 제품군을 받고, 하위 티어 사용자는 축소된 기능을 제공받습니다.

플랜별 엔드포인트

예를 들어, 회사 검색( /companies)은 PRO 이상 플랜이 필요하고, 도시 검색은 모든 플랜에서 사용할 수 있습니다.

다음 단계

우리는 현재 1,680만 SIRENE 사업장을 가져왔고 35,000개 이상의 커뮌을 색인했습니다. API는 지리 데이터, 주소 검증, 기업 검색, 관리/청구 등 39 엔드포인트를 처리합니다.

프랑스 주소나 기업 데이터를 다루는 무언가를 구축하고 있다면, 한 번 시도해 보세요:

  • 무료 티어: 하루 100 요청, 신용카드 필요 없음
  • 문서: (link pending)
  • 가입: (link pending)
  • 예시: (link pending)

다음 기사에서는 PostgreSQL 트라이그램 인덱스를 사용해 1,680만 SIRENE 사업장66 ms에 조회하는 방법을 자세히 살펴볼 것입니다.

AZMORIS Engineering — “지속되는 소프트웨어”

0 조회
Back to Blog

관련 글

더 보기 »

연구 데스크의 메모리 문제

왜 증권사는 또 다른 대시보드가 아니라 뇌가 필요했는가. 한 분석가가 책상 너머로 몸을 기울이며 묻는다: “XYZ Inc—파일을 제출한 그 회사에 대한 현재 입장은 무엇인가요?”

Archify 소개: 아키텍처 아이디어에서 Spring Boot 코드까지

문제 모든 백엔드 개발자는 이 순간을 경험해 본 적이 있다: 새로운 프로젝트를 시작하면서 이미 아키텍처를 염두에 두고—아마도 간단한 REST 서비스와 몇 개의 엔드포인트만을 생각하고—시작한다. 하지만 프로젝트가 진행될수록 요구사항이 늘어나고, 복잡도가 급격히 증가한다. ### 원인 1. **초기 설계 부족** - 프로젝트 초기에 충분한 도메인 모델링과 데이터 흐름 설계를 하지 않으면, 나중에 구조를 바꾸는 데 큰 비용이 든다. 2. **기능 폭발** - 초기에는 “읽기 전용” API만 필요했지만, 곧 쓰기, 인증, 권한, 실시간 알림 등 다양한 기능이 추가된다. 3. **기술 스택 변화** - 새로운 라이브러리나 프레임워크가 등장하면서 기존 코드를 교체하거나 통합해야 하는 상황이 발생한다. ### 해결책 #### 1. 도메인 중심 설계(Domain‑Driven Design) 적용 - **Bounded Context**를 명확히 정의하고, 각 컨텍스트마다 독립적인 모델과 서비스 계층을 만든다. - **Entity**, **Value Object**, **Aggregate** 등을 활용해 비즈니스 로직을 캡슐화한다. #### 2. 레이어드 아키텍처 도입 - **Presentation Layer** (Controller / Handler) – HTTP 요청을 받아 DTO 로 변환하고, 서비스에 위임한다. - **Application Layer** – 트랜잭션 경계와 워크플로우를 정의한다. - **Domain Layer** – 핵심 비즈니스 로직과 도메인 규칙을 구현한다. - **Infrastructure Layer** – DB, 메시지 브로커, 외부 API 등 기술적 세부 사항을 담당한다. #### 3. 인터페이스와 의존성 역전(Dependency Inversion) - 상위 레이어가 하위 레이어에 의존하지 않도록 **Repository Interface**, **Service Interface** 등을 정의하고, 구현은 인프라 레이어에 둔다. - 스프링에서는 `@Autowired` 대신 **Constructor Injection**을 사용하고, 테스트 시에는 **Mock** 구현체를 주입한다. #### 4. 모듈화와 마이크로서비스 전략 - 시스템이 일정 규모를 넘어가면 **Domain‑Driven Microservices** 혹은 **Modular Monolith** 로 전환한다. - 각 모듈(또는 서비스)은 독립적인 데이터베이스 스키마와 배포 파이프라인을 갖는다. #### 5. 자동화된 테스트와 CI/CD 파이프라인 - **Unit Test**, **Integration Test**, **Contract Test**(예: Spring Cloud Contract) 를 레이어별로 작성한다. - GitHub Actions, GitLab CI 등으로 **빌드 → 테스트 → 배포** 흐름을 자동화한다. #### 6. 문서화와 코드 규칙 - OpenAPI/Swagger 로 API 스펙을 정의하고, **API‑First** 접근법을 채택한다. - 코드 스타일은 **Checkstyle**, **Spotless**, **EditorConfig** 로 일관성을 유지한다. ### 예시 코드 (Spring Boot) ```java // Domain Layer @Entity public class Order { @Id @GeneratedValue private Long id; private LocalDateTime orderDate; @Embedded private Money totalAmount; // 비즈니스 메서드 public void addItem(Product product, int quantity) { … } } // Application Layer @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepo; private final PaymentGateway paymentGateway; @Transactional public OrderDto placeOrder(CreateOrderCommand cmd) { Order order = new Order(); // 도메인 로직 수행 orderRepo.save(order); paymentGateway.charge(order.getTotalAmount()); return OrderMapper.toDto(order); } } // Infrastructure Layer @Repository public interface OrderRepository extends JpaRepository<Order, Long> {} // Presentation Layer @RestController @RequestMapping('/api/orders') @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @PostMapping public ResponseEntity<OrderDto> create(@RequestBody @Valid CreateOrderCommand cmd) { OrderDto result = orderService.placeOrder(cmd); return ResponseEntity.status(HttpStatus.CREATED).body(result); } } ``` ### 마무리 프로젝트 초기에 **아키텍처 설계**와 **도메인 모델링**에 충분한 시간을 투자하면, 이후 기능 추가나 기술 교체가 훨씬 수월해진다. 레이어드 아키텍처와 DDD 원칙을 적용하고, 의존성 역전 및 자동화된 테스트를 기반으로 하면, 복잡도가 증가해도 유지보수 가능한 코드를 유지할 수 있다. **핵심 포인트** - 초기 설계에 투자 → 장기 비용 절감 - 레이어와 인터페이스로 책임 분리 - 테스트와 CI/CD 로 변경 위험 최소화 - 필요 시 마이크로서비스 혹은 모듈형 모노리식으로 확장