2016-05-31 11 views
2

Ich versuche, eine Scrapping-Anwendung mit bdd, ddd und oop zu entwerfen. Der Zweck dieser App ist es, zu überprüfen, ob eine Seite geöffnet ist oder nicht, ob sie bestimmte Elemente enthält oder nicht, wie Links, Bilder usw.DDD: Modellierung einer Scraping-App

Mit BDD, Schreiben meiner Szenarien, kam ich mit Klassen wie Page, Link, Image usw. mit Eigenschaften wie url, src, alt. Die Frage, die ich habe, ist, dass ich zwei Möglichkeiten sehe, um gegen Live-Websites zu überprüfen: 1. Verwenden Sie eine andere Klasse, eine crawler Klasse, die die in den vorherigen Klassen enthaltenen Daten verwenden und das Internet überprüfen würde Seiten auf, wenn sie die erwarteten Elemente usw. enthalten:

$crawler = new Crawler(); 
$page = new Page($url); 

$pageReturned = $crawler->get($page); 

if ($pageReturned->isUp()) { 
    // continue with the checking of element... 
    $image = new Image($src, $alt); 

    if ($pageReturned->contains($image)) { 
    // check other things 
    } else { 
    // image not found on the page 
    } 
} 
  1. haben diese „kriechen“ Verhalten, die in den Klassen selbst (was mehr sieht aus wie oop mir), was bedeutet, dass ich die Seite fragen würde, ob es oben ist, ob es ein gegebenes Element usw. enthält:

    $page = new Page($url); 
    
    if ($page->isUp()) { 
        $image = new Image($src, $alt); 
    
        if ($page->contains($image)) { 
        // check other things 
        } else { 
        // image not found on the page 
        } 
    } 
    

ich versucht sein würde # 2 zu verwenden, aber ich frage mich, wie ich die Klassen so tun konnte, um eine bestimmte kriechenden Bibliothek gebunden, ohne. Ich würde gerne später zwischen verschiedenen Bibliotheken wechseln können, wie goutte oder guzzle oder sogar direkt curl.

Vielleicht vermisse ich den Punkt von oop insgesamt hier ... Vielleicht gibt es viel bessere/clevere Möglichkeiten, dies zu tun, daher meine Frage. :)

+0

Hier kommt das D in SOLID ins Spiel - Dependency Inversion. Eine höhere Klassenklasse sollte nur von Abstraktionen abhängen, nicht von konkreten Klassen. Wenn die verschiedenen Crawling-Implementierungen eine gemeinsame Schnittstelle implementieren und der aufrufende Code nur für diese Schnittstelle funktioniert, kann die gewünschte konkrete Implementierung nach Bedarf eingefügt werden. – dbugger

+0

Warum sich mit einem komplexen Domänenmodell beschäftigen? Das einzige wahre Objekt in dem Problem ist der "Crawler", der den Zustand beibehalten muss, während er sein Verhalten des "Crawlens" ausführt. Der Rest Ihrer "Objekte" sind nur Datenstrukturen. Hat 'Page' Zustände, zwischen denen es übergeht? Hat es ein Verhalten, das sich während seiner Lebenszeit ändert? Wenn nein, ist es nur eine Datenstruktur, die von Ihrem Crawler konsumiert wird. –

Antwort

3

Eine nützliche Sache zu realisieren ist, dass Ihr Modellcode tendenziell in sich abgeschlossen ist - es weiß über Datenelemente im Modell (dh das Daten-Diagramm) und die Datenkonsistenzregeln, aber nichts anderes.

So Ihr Modell für eine Seite aussehen würde wahrscheinlich wie

class Page { 
    URL uri; 
    ImageCollection images; 
} 

Mit anderen Worten, das Modell weiß um die Beziehung zwischen den Seiten und Bildern, aber es weiß nicht unbedingt das, was diese Dinge in der Praxis bedeuten.

Um Ihr Domänenmodell tatsächlich mit der realen Welt zu vergleichen, übergeben Sie dem Modell einen Dienst, der weiß, wie die Arbeit zu erledigen ist, aber den Status nicht kennt.

class Crawler { 
    void verify(URL page, ImageCollection images) 
} 

Jetzt passen Sie sie zusammen; Sie konstruieren den Crawler und übergeben ihn an die Seite. Die Seite findet seinen Zustand und übergibt diesen Zustand zu dem Crawler

class Page { 
    void verifyWith(Crawler crawler) { 
     crawler.verify(this.uri, this.items); 
    } 
} 

Natürlich, werden Sie wahrscheinlich auf den Crawler nicht zu eng an der Seite zu koppeln wollen; Schließlich möchten Sie vielleicht die Crawler-Bibliotheken austauschen, Sie möchten vielleicht etwas anderes mit dem Seitenstatus tun.

Sie machen also die Signatur dieser Methode allgemeiner; Es akzeptiert eine Schnittstelle und kein Objekt mit einer bestimmten Bedeutung.In dem klassischen Buch Design Patterns, wäre dies ein Beispiel für den Visitor Pattern

class Page { 
    interface Visitor { 
     void visitPage(URL uri, ImageCollection images); 
    } 

    void verifyWith(Visitor visitor) { 
     visitor.visitPage(this.uri, this.images); 
    } 
}  

class Crawler implements Page.Visitor { 
    void visitPage(URL page, ImageCollection images) { 
     .... 
    } 
} 

Hinweis sein - das Modell (Seite) ist verantwortlich für die Integrität ihrer Daten erhalten bleibt. Das bedeutet, dass alle Daten, die an einen Besucher übergeben werden, unveränderlich sein müssen, oder andernfalls eine veränderbare Kopie des Zustands des Modells.

Auf lange Sicht möchten Sie wahrscheinlich nicht die Definition des Visitors in der Seite eingebettet wie folgt. Seite ist Teil der API des Modells, aber der Besucher ist Teil der SPI des Modells.

interface PageVisitor { 
    void visitPage(URL uri, ImageCollection images); 
} 

class Page { 
    void verifyWith(PageVisitor visitor) { 
     visitor.visitPage(this.uri, this.images); 
    } 
}  

class Crawler implements PageVisitor { 
    void visitPage(URL page, ImageCollection images) { 
     .... 
    } 
} 

Eine Sache, die hier beschönigt bekommen haben, ist, dass Sie zwei verschiedene Implementierungen von „Seite“ zu haben scheinen

// Here's one? 
$page = new Page($url); 

// And here is something else? 
$pageReturned = $crawler->get($page); 

Eine der Lehren von ist die der Dinge zu benennen; insbesondere, dass Sie nicht zwei Ideen kombinieren, die wirklich unterschiedliche Bedeutungen haben. In diesem Fall sollten Sie sich darüber im Klaren sein, welcher Typ vom Crawler zurückgegeben wird.

Zum Beispiel, wenn Sie in einer Domäne sind, wo die allgegenwärtige Sprache von REST entlehnt, dann könnten Sie Aussagen, die

aussehen
$representation = $crawler->get($resource); 

In Ihrem Beispiel sucht die Sprache mehr HTML-spezifisch, so könnte dies angemessen sein

$htmlDocument = $crawler->get($page) 

der Grund für die Belichtung dieses: das Dokument/Darstellung paßt gut mit der Vorstellung, ein Wertobjekt des Seins - es ist eine unveränderliche Tasche unveränderlichen Zeugs; Sie können die "Seite" nicht ändern, indem Sie das HTML-Dokument in irgendeiner Weise manipulieren.

Wertobjekte sind reine Query-Oberflächen - jede Methode auf ihnen, die wie eine Mutation aussieht, ist wirklich eine Abfrage, die eine neue Instanz des Typs zurückgibt.

Wertobjekte sind eine gute Passform für die Spezifikation Muster von plalx in seiner Antwort beschrieben:

HtmlSpecification { 
    boolean isSatisfiedBy(HtmlDocument); 
} 
+0

Vielen Dank für Ihre Antwort und Erklärung. Die Sache ist, dass es mir schwer fällt zu sehen, wo ich zum Beispiel die Ergebnisse der Seite mit einem bestimmten Bild bekommen würde. Wenn ich es gut verstehe, würde die Methode 'visitPage' des Crawlers aufgerufen, wenn' Page-> verifyWith ($ crawler) 'aufgerufen wird, aber da beide Methoden nichts zurückliefern, würde ich eine Methode aufrufen, um einen booleschen Wert zu erhalten, der mir das Ergebnis liefert der Prüfung/Verifizierung? –

+0

Die allgemeinste Antwort: Abfrage des Status des Crawlers, nachdem die Seite verifiziert wurde. visitPage ist ein Befehl, der den Status des Crawlers ändern kann, z. B. Hinzufügen von Validierungsfehlern zu einer Liste. Sie können dann überprüfen, ob diese Liste leer ist. – VoiceOfUnreason

1

Was diese Suchbegriffe? Sie können jedes vorhandene HTML-Parsing-Framework verwenden, das ein Dokumentobjektmodell erstellen kann, das über CSS-Selektoren abgefragt werden kann, und die Implementierung hinter Domänenschnittstellen abstrahieren.

Ich verwendete auch das Spezifikationsmuster, um übereinstimmende Kriterien für Seiten zu erstellen, die es sehr einfach machen würden, neue Regeln zu erstellen.

enter image description here

Verbrauch:

var elementsQuery = new ElementsQuery('image[src="someImage.png"], a[href="http://www.google.com"]'); 
var spec = new PageAvailable().and(new ContainsElements(elementQuery, 2)); 
var page = pageLoader.load(url); 

if (spec.isSatisfiedBy(page)) { 
    //Page is available & the page contains exactly one image with the attribute src=someImage.png and one link to google 
} 

Einige Dinge, die Sie tun können, das Design zu verbessern, ist eine flüssige Builder zu erstellen, die Sie CSS-Selektoren (ElementsQuery) leichter generieren können.

z.

var elementsQuery = new ElementsQueryBuilder() 
         .match('image').withAttr('src', 'someImage.png') 
         .match('a').withAttr('href', 'http://www.google.com'); 

Anothing wichtige Sache, wenn Sie schließlich in der Lage sein wollen, Spezifikationen zu erstellen, die eine leistungsfähigere API aussetzen würde darüber hinaus die Validierung der Existenz von Elementen durch eine ElementsQuery geht das Document Object Model (DOM) zu inspizieren.

Sie könnten etwas wie das DOM im obigen Design ersetzen und die PageSpecification API entsprechend anpassen, um den Spezifikationen mehr Leistung zu geben.

public interface Element { 
    public String tag(); 
    public String attrValue(String attr); 
    public boolean containsElements(ElementsQuery query, ExpectedCount count); 
    public Elements queryElements(ElementsQuery query); 
    public Elements children(); 
} 

Der Vorteil aus der Domäne zugänglich Zugriff auf die gesamte DOM-Struktur nicht nur einen Infrastruktur-Service gefragt werden, ob die Kriterien erfüllt sind, ist, dass die Spezifikationen Erklärung und Umsetzung sowohl in der Domäne leben können.

In der Antwort von @ VoiceOfUnreason muss die Implementierung Crawler in der Infrastrukturschicht leben und während die Deklaration der Regeln in der Domäne (ImageCollection) lebt, wird die Logik zum Überprüfen dieser Regeln in der Infrastruktur gespeichert.

Schließlich nehme ich an, dass die Seitenüberwachungseinträge wahrscheinlich persistent sind und möglicherweise über eine UI oder eine Konfigurationsdatei konfigurierbar sind.

Was ich vielleicht tun würde, ist zwei verschiedene begrenzte Kontexte zu haben. Eine, um zu überwachende Seiten mit der zugehörigen Spezifikation zu verwalten (Page ist eine Entität in diesem Kontext) und ein anderer Kontext, der für die Überwachung zuständig ist (Page ist ein Wert in diesem Zusammenhang - eine ähnliche Implementierung wie beschrieben).

+0

@VoiceOfUnreason Was denkst du? – plalx

+0

Nicht schlecht. Die Bindung zwischen der Bild- und der Element-Abfrage ist nicht ganz richtig; ein flüssiger Erbauer könnte das aufräumen (macht aber das Beispiel schwerer verständlich). Außerdem muss die Spezifikation in der Lage sein, den Status des Subjekts abzufragen. – VoiceOfUnreason

+0

Und Sie sollten einige Links zu Aufsätzen auf dem Muster enthalten :) – VoiceOfUnreason