Heim  >  Artikel  >  Backend-Entwicklung  >  Refactoring basierend auf Designprinzipien: Beispiel eines Datenerfassungs-Crawler-Systems

Refactoring basierend auf Designprinzipien: Beispiel eines Datenerfassungs-Crawler-Systems

WBOY
WBOYOriginal
2024-07-20 07:38:49734Durchsuche

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

Einführung

Die Verbesserung der Codequalität ist bei der Softwareentwicklung immer ein wichtiges Thema. In diesem Artikel nehmen wir ein Datenerfassungs-Crawler-System als Beispiel und erklären konkret, wie Designprinzipien und Best Practices durch schrittweises Refactoring angewendet werden.

Code vor der Verbesserung

Zuerst beginnen wir mit einem sehr einfachen Web-Scraper, bei dem alle Funktionen in einer Klasse integriert sind.

Übersetzt mit DeepL.com (kostenlose Version)

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()

Punkte, die verbessert werden müssen

  1. Verstößt gegen das Prinzip der Einzelverantwortung: Eine Klasse ist für die gesamte Datenerfassung, -analyse, -anreicherung und -speicherung verantwortlich
  2. Unklare Geschäftslogik: Die Geschäftslogik ist in die Enrich_Data-Methode eingebettet, aber mit anderen Verarbeitungen vermischt
  3. Mangelnde Wiederverwendbarkeit: Funktionen sind eng miteinander verbunden, was die individuelle Wiederverwendung schwierig macht
  4. Testschwierigkeiten: Es ist schwierig, einzelne Funktionen unabhängig voneinander zu testen
  5. Konfigurationssteifigkeit: Datenbankpfade und andere Einstellungen sind direkt in den Code eingebettet

Refactoring-Phase

1. Aufgabentrennung: Trennung von Datenerfassung, -analyse und -speicherung

  • Große Änderung: Aufteilung der Verantwortlichkeiten für Datenerfassung, -analyse und -speicherung in separate Klassen
  • Ziel: Prinzip der Einzelverantwortung anwenden, Umgebungsvariablen einführen

Verzeichnisstruktur

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

Diese Änderung verdeutlicht die Verantwortlichkeiten jeder Klasse und verbessert die Wiederverwendbarkeit und Testbarkeit. Die Geschäftslogik ist jedoch weiterhin in die DataEnricher-Klasse eingebettet.

2. Einführung von Schnittstellen und Abhängigkeitsinjektion

  • Hauptänderung: Schnittstellen einführen und Abhängigkeitsinjektion implementieren.
  • Zweck: Flexibilität und Erweiterbarkeit erhöhen, Umgebungsvariablen erweitern, Geschäftslogik abstrahieren

Verzeichnisstruktur

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

interfaces/data_fetcher_interface.py

from abc import ABC, abstractmethod

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

interfaces/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

interfaces/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

interfaces/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

implementations/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

Die wichtigsten Änderungen in dieser Phase sind

  1. Einführung einer Schnittstelle, um den Wechsel zu verschiedenen Implementierungen zu erleichtern
  2. Abhängigkeitsinjektion, um die WebScraper-Klasse flexibler zu machen
  3. Die fetch_data-Methode wurde geändert, um die URL als Argument zu verwenden, wodurch die URL-Spezifikation flexibler wird.
  4. Geschäftslogik wurde als DataEnricherInterface abstrahiert und als KeywordDataEnricher implementiert.
  5. Die Geschäftslogik wurde flexibler gestaltet, indem Schlüsselwörter mithilfe von Umgebungsvariablen festgelegt werden können.

Diese Änderungen haben die Flexibilität und Erweiterbarkeit des Systems erheblich verbessert. Die Geschäftslogik bleibt jedoch im DataEnricherInterface und seiner Implementierung eingebettet. Der nächste Schritt besteht darin, diese Geschäftslogik weiter zu trennen und klar als Domänenschicht zu definieren.

3. Einführung der Domänenschicht und Trennung der Geschäftslogik

Im vorherigen Schritt wurde durch die Einführung von Schnittstellen die Flexibilität des Systems erhöht. Die Geschäftslogik (in diesem Fall die Bestimmung und Filterung der Datenwichtigkeit) wird jedoch weiterhin als Teil der Datenschicht behandelt. Basierend auf dem Konzept des domänengesteuerten Designs bietet die Behandlung dieser Geschäftslogik als zentrales Konzept des Systems und ihre Implementierung als unabhängige Domänenschicht die folgenden Vorteile

  1. Zentralisierte Verwaltung der Geschäftslogik
  2. Aussagekräftigerer Code durch das Domänenmodell
  3. größere Flexibilität bei sich ändernden Geschäftsregeln
  4. einfaches Testen

Aktualisierte Verzeichnisstruktur:

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

Zu diesem Zeitpunkt werden die Rollen von DataEnricherInterface und KeywordDataEnricher auf das ScrapedData-Modell und DataEnrichmentService auf der Domänenebene verschoben. Einzelheiten zu dieser Änderung finden Sie unten.

Vor der Änderung (Abschnitt 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()}

Nach der Änderung (Abschnitt 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

Diese Änderung verbessert Folgendes.

  1. Die Geschäftslogik wurde auf die Domänenebene verschoben, sodass kein DataEnricherInterface erforderlich ist.

  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.

Das obige ist der detaillierte Inhalt vonRefactoring basierend auf Designprinzipien: Beispiel eines Datenerfassungs-Crawler-Systems. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn