>  기사  >  백엔드 개발  >  디자인 원칙에 따른 리팩토링: 데이터 수집 크롤러 시스템의 예

디자인 원칙에 따른 리팩토링: 데이터 수집 크롤러 시스템의 예

WBOY
WBOY원래의
2024-07-20 07:38:49734검색

Refactoring based on design principles: example of a data collection crawler system

소개

코드 품질을 향상시키는 것은 소프트웨어 개발에서 항상 중요한 문제입니다. 이 글에서는 데이터 수집 크롤러 시스템을 예로 들어 단계별 리팩토링을 통해 디자인 원칙과 모범 사례를 적용하는 방법을 구체적으로 설명합니다.

개선 전 코드

먼저 모든 기능이 하나의 클래스에 통합된 매우 간단한 웹 스크레이퍼로 시작합니다.

DeepL.com으로 번역됨(무료 버전)

project_root/
├── web_scraper.py
├── main.py
└── requirements.txt

web_scraper.py

import requests
import json
import sqlite3

class WebScraper:
    def __init__(self, url):
        self.url = url

    def fetch_data(self):
        response = requests.get(self.url)
        data = response.text
        parsed_data = self.parse_data(data)
        enriched_data = self.enrich_data(parsed_data)
        self.save_data(enriched_data)
        return enriched_data

    def parse_data(self, data):
        return json.loads(data)

    def enrich_data(self, data):
        # Apply business logic here
        # Example: extract only data containing specific keywords
        return {k: v for k, v in data.items() if 'important' in v.lower()}

    def save_data(self, data):
        conn = sqlite3.connect('test.db')
        cursor = conn.cursor()
        cursor.execute('INSERT INTO data (json_data) VALUES (?)', (json.dumps(data),))
        conn.commit()
        conn.close()

main.py

from web_scraper import WebScraper

def main():
    scraper = WebScraper('https://example.com/api/data')
    data = scraper.fetch_data()
    print(data)

if __name__ == "__main__":
    main()

개선점

  1. 단일 책임 원칙 위반: 하나의 클래스가 모든 데이터 수집, 분석, 강화 및 저장을 담당합니다
  2. 불분명한 비즈니스 로직: 비즈니스 로직이 rich_data 메소드에 포함되어 있지만 다른 처리와 혼합되어 있습니다
  3. 재사용성 부족: 기능이 긴밀하게 결합되어 개별 재사용이 어렵습니다
  4. 테스트 어려움: 개별 기능을 독립적으로 테스트하기 어려움
  5. 구성 견고성: 데이터베이스 경로 및 기타 설정이 코드에 직접 포함됩니다

리팩토링 단계

1. 책임 분리: 데이터 수집, 분석, 저장의 분리

  • 주요 변경 사항: 데이터 수집, 분석, 저장에 대한 책임을 별도의 클래스로 분리
  • 목표: 단일 책임 원칙 적용, 환경 변수 도입

디렉토리 구조

project_root/
├── data_fetcher.py
├── data_parser.py
├── data_saver.py
├── data_enricher.py
├── web_scraper.py
├── main.py
└── requirements.txt

data_enricher.py

class DataEnricher:
    def enrich(self, data):
        return {k: v for k, v in data.items() if 'important' in v.lower()}

web_scraper.py

from data_fetcher import DataFetcher
from data_parser import DataParser
from data_enricher import DataEnricher
from data_saver import DataSaver

class WebScraper:
    def __init__(self, url):
        self.url = url
        self.fetcher = DataFetcher()
        self.parser = DataParser()
        self.enricher = DataEnricher()
        self.saver = DataSaver()

    def fetch_data(self):
        raw_data = self.fetcher.fetch(self.url)
        parsed_data = self.parser.parse(raw_data)
        enriched_data = self.enricher.enrich(parsed_data)
        self.saver.save(enriched_data)
        return enriched_data

이번 변경으로 각 클래스의 책임이 명확해지고 재사용성과 테스트 가능성이 향상되었습니다. 그러나 비즈니스 로직은 여전히 ​​DataEnricher 클래스에 포함되어 있습니다.

2. 인터페이스 도입 및 의존성 주입

  • 주요 변경사항: 인터페이스 도입 및 종속성 주입 구현
  • 목적: 유연성과 확장성 증가, 환경 변수 확장, 비즈니스 로직 추상화

디렉토리 구조

project_root/
├── interfaces/
│   ├── __init__.py
│   ├── data_fetcher_interface.py
│   ├── data_parser_interface.py
│   ├── data_enricher_interface.py
│   └── data_saver_interface.py
├── implementations/
│   ├── __init__.py
│   ├── http_data_fetcher.py
│   ├── json_data_parser.py
│   ├── keyword_data_enricher.py
│   └── sqlite_data_saver.py
├── web_scraper.py
├── main.py
└── requirements.txt

인터페이스/data_fetcher_interface.py

from abc import ABC, abstractmethod

class DataFetcherInterface(ABC):
    @abstractmethod
    def fetch(self, url: str) -> str:
        pass

인터페이스/data_parser_interface.py

from abc import ABC, abstractmethod
from typing import Dict, Any

class DataParserInterface(ABC):
    @abstractmethod
    def parse(self, raw_data: str) -> Dict[str, Any]:
        pass

인터페이스/data_enricher_interface.py

from abc import ABC, abstractmethod
from typing import Dict, Any

class DataEnricherInterface(ABC):
    @abstractmethod
    def enrich(self, data: Dict[str, Any]) -> Dict[str, Any]:
        pass

인터페이스/data_saver_interface.py

from abc import ABC, abstractmethod
from typing import Dict, Any

class DataSaverInterface(ABC):
    @abstractmethod
    def save(self, data: Dict[str, Any]) -> None:
        pass

구현/keyword_data_enricher.py

import os
from interfaces.data_enricher_interface import DataEnricherInterface

class KeywordDataEnricher(DataEnricherInterface):
    def __init__(self):
        self.keyword = os.getenv('IMPORTANT_KEYWORD', 'important')

    def enrich(self, data):
        return {k: v for k, v in data.items() if self.keyword in str(v).lower()}

web_scraper.py

from interfaces.data_fetcher_interface import DataFetcherInterface
from interfaces.data_parser_interface import DataParserInterface
from interfaces.data_enricher_interface import DataEnricherInterface
from interfaces.data_saver_interface import DataSaverInterface

class WebScraper:
    def __init__(self, fetcher: DataFetcherInterface, parser: DataParserInterface, 
                 enricher: DataEnricherInterface, saver: DataSaverInterface):
        self.fetcher = fetcher
        self.parser = parser
        self.enricher = enricher
        self.saver = saver

    def fetch_data(self, url):
        raw_data = self.fetcher.fetch(url)
        parsed_data = self.parser.parse(raw_data)
        enriched_data = self.enricher.enrich(parsed_data)
        self.saver.save(enriched_data)
        return enriched_data

이 단계의 주요 변경 사항은

  1. 다른 구현으로의 전환을 용이하게 하는 인터페이스 도입
  2. WebScraper 클래스를 더욱 유연하게 만들기 위한 종속성 주입
  3. fetch_data 메소드가 url을 인수로 사용하도록 변경되어 URL 지정이 더욱 유연해졌습니다.
  4. 비즈니스 로직이 DataEnricherInterface로 추상화되어 키워드DataEnricher로 구현되었습니다.
  5. 환경 변수를 사용해 키워드를 설정할 수 있어 비즈니스 로직이 더욱 유연해졌습니다.

이러한 변경으로 시스템의 유연성과 확장성이 크게 향상되었습니다. 그러나 비즈니스 논리는 DataEnricherInterface 및 해당 구현에 계속 포함되어 있습니다. 다음 단계는 이 비즈니스 로직을 더욱 분리하여 도메인 레이어로 명확하게 정의하는 것입니다.

3. 도메인 레이어 도입 및 비즈니스 로직 분리

이전 단계에서는 인터페이스를 도입하여 시스템의 유연성을 높였습니다. 그러나 비즈니스 로직(이 경우 데이터 중요도 결정 및 필터링)은 여전히 ​​데이터 계층의 일부로 처리됩니다. 도메인 중심 설계 개념을 바탕으로 이러한 비즈니스 로직을 시스템의 중심 개념으로 취급하고 이를 독립된 도메인 레이어로 구현하면 다음과 같은 이점을 얻을 수 있습니다

  1. 비즈니스 로직의 중앙 집중식 관리
  2. 도메인 모델을 통해 코드 표현이 더욱 풍부해졌습니다
  3. 비즈니스 규칙 변경에 대한 유연성 향상
  4. 테스트 용이성

업데이트된 디렉토리 구조:

project_root/
├── domain/
│   ├── __init__.py
│   ├── scraped_data.py
│   └── data_enrichment_service.py
├── data/
│   ├── __init__.py
│   ├── interfaces/
│   │   ├── __init__.py
│   │   ├── data_fetcher_interface.py
│   │   ├── data_parser_interface.py
│   │   └── data_saver_interface.py
│   ├── implementations/
│   │   ├── __init__.py
│   │   ├── http_data_fetcher.py
│   │   ├── json_data_parser.py
│   │   └── sqlite_data_saver.py
├── application/
│   ├── __init__.py
│   └── web_scraper.py
├── main.py
└── requirements.txt

이 단계에서는 DataEnricherInterface 및 KeyDataEnricher의 역할이 도메인 계층의 ScrapedData 모델 및 DataEnrichmentService로 이동됩니다. 이번 변경 사항에 대한 자세한 내용은 아래에 나와 있습니다.

변경 전(2항)

class DataEnricherInterface(ABC):
    @abstractmethod
    def enrich(self, data: Dict[str, Any]) -> Dict[str, Any]:
        pass
class KeywordDataEnricher(DataEnricherInterface):
    def __init__(self):
        self.keyword = os.getenv('IMPORTANT_KEYWORD', 'important')

    def enrich(self, data):
        return {k: v for k, v in data.items() if self.keyword in str(v).lower()}

수정 후(3항)

@dataclass
class ScrapedData:
    content: Dict[str, Any]
    source_url: str

    def is_important(self) -> bool:
        important_keyword = os.getenv('IMPORTANT_KEYWORD', 'important')
        return any(important_keyword in str(v).lower() for v in self.content.values())
class DataEnrichmentService:
    def __init__(self):
        self.important_keyword = os.getenv('IMPORTANT_KEYWORD', 'important')

    def enrich(self, data: ScrapedData) -> ScrapedData:
        if data.is_important():
            enriched_content = {k: v for k, v in data.content.items() if self.important_keyword in str(v).lower()}
            return ScrapedData(content=enriched_content, source_url=data.source_url)
        return data

이번 변경으로 다음 사항이 개선되었습니다.

  1. 비즈니스 로직이 도메인 레이어로 이동되어 DataEnricherInterface가 필요하지 않습니다.

  2. the KeywordDataEnricher functionality has been merged into the DataEnrichmentService, centralizing the business logic in one place.

  3. The is_important method has been added to the ScrapedData model. This makes the domain model itself responsible for determining the importance of data and makes the domain concept clearer.

  4. DataEnrichmentService now handles ScrapedData objects directly, improving type safety.

The WebScraper class will also be updated to reflect this change.

from data.interfaces.data_fetcher_interface import DataFetcherInterface
from data.interfaces.data_parser_interface import DataParserInterface
from data.interfaces.data_saver_interface import DataSaverInterface
from domain.scraped_data import ScrapedData
from domain.data_enrichment_service import DataEnrichmentService

class WebScraper:
    def __init__(self, fetcher: DataFetcherInterface, parser: DataParserInterface, 
                 saver: DataSaverInterface, enrichment_service: DataEnrichmentService):
        self.fetcher = fetcher
        self.parser = parser
        self.saver = saver
        self.enrichment_service = enrichment_service

    def fetch_data(self, url: str) -> ScrapedData:
        raw_data = self.fetcher.fetch(url)
        parsed_data = self.parser.parse(raw_data)
        scraped_data = ScrapedData(content=parsed_data, source_url=url)
        enriched_data = self.enrichment_service.enrich(scraped_data)
        self.saver.save(enriched_data)
        return enriched_data

This change completely shifts the business logic from the data layer to the domain layer, giving the system a clearer structure. The removal of the DataEnricherInterface and the introduction of the DataEnrichmentService are not just interface replacements, but fundamental changes in the way business logic is handled.

Summary

This article has demonstrated how to improve code quality and apply design principles specifically through a step-by-step refactoring process for the data collection crawler system. The main areas of improvement are as follows.

  1. Separation of Responsibility: Applying the principle of single responsibility, we separated data acquisition, parsing, enrichment, and storage into separate classes.
  2. Introduction of interfaces and dependency injection: greatly increased the flexibility and scalability of the system, making it easier to switch to different implementations.
  3. Introduction of domain model and services: clearly separated the business logic and defined the core concepts of the system.
  4. Adoption of Layered Architecture: Clearly separated the domain, data, and application layers and defined the responsibilities of each layer. 5.Maintain interfaces: Maintained abstraction at the data layer to ensure flexibility in implementation.

These improvements have greatly enhanced the system's modularity, reusability, testability, maintainability, and scalability. In particular, by applying some concepts of domain-driven design, the business logic became clearer and the structure was more flexible to accommodate future changes in requirements. At the same time, by maintaining the interfaces, we ensured the flexibility to easily change and extend the data layer implementation.

It is important to note that this refactoring process is not a one-time event, but part of a continuous improvement process. Depending on the size and complexity of the project, it is important to adopt design principles and DDD concepts at the appropriate level and to make incremental improvements.

Finally, the approach presented in this article can be applied to a wide variety of software projects, not just data collection crawlers. We encourage you to use them as a reference as you work to improve code quality and design.

위 내용은 디자인 원칙에 따른 리팩토링: 데이터 수집 크롤러 시스템의 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.