This commit is contained in:
qvalentin 2022-05-27 09:54:20 +02:00
parent 22a9368697
commit b0d3808fcb
Signed by: qvalentin
GPG Key ID: C979FA1EAFCABF1C
5 changed files with 186 additions and 17 deletions

View File

@ -598,16 +598,21 @@ void addCommandWorks() {
var category1 = "funStuff";
var category2 = "workStuff";
ArgumentCaptor<String> captureUrl = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> captureUsername = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Set<String>> captureCategories = ArgumentCaptor.forClass(Set.class);
ArgumentCaptor<String> captureUrl =
ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> captureUsername =
ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Set<String>> captureCategories =
ArgumentCaptor.forClass(Set.class);
doNothing()
.when(mockAdapter)
.addLink(captureUrl.capture(), captureCategories.capture(), captureUsername.capture());
.addLink(captureUrl.capture(),
captureCategories.capture(), captureUsername.capture());
var sut = new LinkCommands(mockAdapter);
var returnValue = sut.executeSubcommand(new String[]{"add", url, username, category1, category2});
var returnValue = sut.executeSubcommand(new String[]{"add",
url, username, category1, category2});
assertEquals("Added the new Link", returnValue);
@ -657,11 +662,13 @@ zusätzlich jeweils UML Diagramm der Klasse]
@Test
void addCommandWorks() {
var categoryName = "funStuff";
ArgumentCaptor<String> valueCapture = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> valueCapture =
ArgumentCaptor.forClass(String.class);
doNothing().when(mockAdapter).addCategory(valueCapture.capture());
var sut = new CategoryCommands(mockAdapter);
var returnValue = sut.executeSubcommand(new String[]{"add", categoryName});
var returnValue = sut.executeSubcommand(
new String[]{"add", categoryName});
assertEquals(categoryName, valueCapture.getValue());
assertEquals("Added the new category", returnValue);
@ -670,14 +677,17 @@ zusätzlich jeweils UML Diagramm der Klasse]
@Test
void getCommandWorks() {
var sut = new CategoryCommands(mockAdapter);
when(mockAdapter.getCategories()).thenReturn(Set.of("funStuff", "workStuff"));
when(mockAdapter.getCategories()).thenReturn(
Set.of("funStuff", "workStuff"));
var returnValue = sut.executeSubcommand(new String[]{"get"});
var expected =
"Available Categories:" + System.lineSeparator() + "funStuff" + System.lineSeparator() + "workStuff";
"Available Categories:" + System.lineSeparator() +
"funStuff" + System.lineSeparator() + "workStuff";
var expectedDifferentOrder =
"Available Categories:" + System.lineSeparator() + "workStuff" + System.lineSeparator() + "funStuff";
"Available Categories:" + System.lineSeparator() +
"workStuff" + System.lineSeparator() + "funStuff";
assertTrue(expected.equals(returnValue) || expectedDifferentOrder.equals(returnValue));
}
}
@ -709,11 +719,22 @@ zusätzlich jeweils UML Diagramm der Klasse]
[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung
und kurze Begründung, warum es zur Ubiquitous Language gehört]
| Bezeichung | Bedeutung | Begründung |
| Link | Steht für eine eindeutige URL, die von der Anwendung gespeichert werden soll | Gehört zur Ubiquitous-Laguage, weil Link außerhalb der Domäne der Anwendung auch anders verwendet werden kann |
| Category | Steht für eine Kategorie, die einem Link zugeordnet werden kann | Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, wofür die Kategorien verwendet werden |
| Tag | Steht für einen Tag der automatisch bestimmten Link-Typen zugeordnet wird | Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, was genau getaggt wird und wie dies geschieht |
| *TagMatcher | Steht für eine spezielle Implementation des Interfaces TagMatcher (z.B. GitHubTagMatcher), das dafür sorgt, dass einem Link ein Tag zugewiesen werden kann | Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, auf welche Tag sich die Funktion bezieht und was deren Bedeutung ist. |
**** Link
**Bedeutung**: Steht für eine eindeutige URL, die von der Anwendung gespeichert werden soll
**Begründung**: Gehört zur Ubiquitous-Laguage, weil Link außerhalb der Domäne der Anwendung auch anders verwendet werden kann
**** Category
**Bedeutung**: Steht für eine Kategorie, die einem Link zugeordnet werden kann
**Begründung**: Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, wofür die Kategorien verwendet werden
**** Tag
**Bedeutung**: Steht für einen Tag der automatisch bestimmten Link-Typen zugeordnet wird
**Begründung**: Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, was genau getaggt wird und wie dies geschieht
**** *TagMatcher
**Bedeutung**: Steht für eine spezielle Implementation des Interfaces TagMatcher (z.B. GitHubTagMatcher), das dafür sorgt, dass einem Link ein Tag zugewiesen werden kann
**Begründung**: Gehört zur Ubiquitous-Laguage, weil nur im Kontext der Anwendung klar ist, auf welche Tag sich die Funktion bezieht und was deren Bedeutung ist.
*** Entities
:PROPERTIES:
@ -723,6 +744,16 @@ und kurze Begründung, warum es zur Ubiquitous Language gehört]
keine Entity vorhanden: ausführliche Begründung, warum es keines geben
kann/hier nicht sinnvoll ist]
[[./uml/Link.png]]
Die Klasse Link ist ein Entity, da sie eindeutig über ihre Id LinkId identifizierbar ist und andere Eingenschaften hat, die nicht identifizierend sind.
Man hätte für die Id letztlich auch die URL verwenden können, wenn man verhindern wollte, dass zwei Links mit gleicher URL gespeichert werden, es wurde sich aber gegen diesen "natürlichen" Schlüssel entschieden.
Stattdessen werden die Schlüssel zufällig generiert.
Links werden genutzt um URL und dazugehörige Informationen für die Organisation, wie beispielsweise die Verknüpfung zu Kategorien und Tags zu speichern.
Auch wenn es bisher nicht implementiert ist, könnten Links verändert werden (indem beispielsweise eine neue Kategorie hinzugefügt wird), weshalb ein Entity geeignet ist.
Mit den Funktionen wasCreatedBy,hasTagName,hasCategoryId wird Verhalten direkt im Entity beschrieben.
Die Getter sind hauptsächlich für die Konvertierung zum Ausgabe- bzw. Persistenzformat.
*** Value Objects
:PROPERTIES:
:CUSTOM_ID: value-objects
@ -731,7 +762,15 @@ kann/hier nicht sinnvoll ist]
falls kein Value Object vorhanden: ausführliche Begründung, warum es
keines geben kann/hier nicht sinnvoll ist]/
Klasse: Category Name
[[./uml/LinkUrl.png]]
Die Klasse LinkUrl ist ein ValueObject, dass den Wert einer URl repräsentiert.
Um die Domänenregeln zu überprüfen wird die Java Klasse URL verwendet.
Die Gleichheit zweier LinkUrls ergibt sich, daraus, ob beide dem selben String entsprechen.
Wenn die Domäne dies so will könnte man beispielsweise bei Vergleich auch das Protokoll vernachlässigen.
Mit der Methode hostEquals können beispielsweise zwei Urls verglichen werden, ob sie den gleichen Host haben, dies wird dazu genutzt, die Links nach Host gruppieren zu können.
*** Repositories
:PROPERTIES:
@ -741,6 +780,17 @@ Klasse: Category Name
falls kein Repository vorhanden: ausführliche Begründung, warum es
keines geben kann/hier nicht sinnvoll ist]
[[./uml/LinkRepository.png]]
Die Klasse LinkRepository ist ein Repository.
Sie bietet Zugriff auf die zur Laufzeit des Programmes im Arbeitsspeicher gehaltenen Link Objekte und ist gleichzeitig die Schnittstelle (ein Adapter ist noch dazwischen) zum persistenten CSV-Datei Repository (Klasse: GenericCSVDAO).
Alle Domänenspezifischen Abfragen an die Daten (getById, getByUser, getByUrl ...) und Veränderungen werden in diesem Repository durchgeführt.
Das CSV-Datei Repository hat dagegen nur reine Create, Read, (Update) und Delete Funktionalität anhand des Schlüssels einer Entity.
Dass das Programm sämtliche Daten im Arbeitsspeicher hält ist eine diskutable Designentscheidung.
Aufgrund der eher geringen zu erwartenden Datenmengen sollte es keine Probleme mit benötigtem Arbeitsspeicher geben.
Die Vorteile sind, dass in der Domäne direkt auf den Objekten gearbeitet werden kann und nicht erst eine Anfrage an die Pluginschicht gestellt werden muss.
Auch kann die Domänenspezifische Abfragelogik in der Domäne implementiert werden und die Persitenz-Repositories haben nur eine reine CRUD Funktionalität.
*** Aggregates
:PROPERTIES:
:CUSTOM_ID: aggregates
@ -749,6 +799,14 @@ keines geben kann/hier nicht sinnvoll ist]
kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben
kann/hier nicht sinnvoll ist]
Bei der Implementierung gibt es keine Klasse, die ein Aggregate widerspiegelt, da jedes Aggregat aus genau einem Entity besteht und somit eine reine Wrapper-Klasse unnötig ist.
So verwaltet das CategoryRepository genau das Entity Category und das LinkRepository das Entity Link.
Die Assoziationen von Links zu Categorys erfolgt indirekt über die IDs der Catgegorys und nicht über direkte Objektreferenzen, was quasi dem Ansatz von Aggregates entspricht.
Das TagMatcherRepository verwaltet mit den CustomTags auch genau ein Entity und hält zusätzlich noch zur Laufzeit unveränderte Statische TagMatcher.
Grundsätzlich könnten Aggregates eingesetzt werden, aufgrund der geringen Komplexität der Daten würden sie die Implementierung aber vermutlich nur unnötig verkomplizieren.
* Kapitel 7: Refactoring
:PROPERTIES:
:CUSTOM_ID: kapitel-7-refactoring
@ -802,6 +860,69 @@ public Optional<Link> getByUrl(LinkUrl url) {
#+end_src
Das Refactoring wurde mit Commit [[https://tea.filefighter.de/qvalentin/LinkDitch/commit/e4f167074250e791f293eb834a60eb9f63a34664][e4f1670742]] durchgeführt.
**** Long Method
Die Funktion im LinkUseCase, welche alle Links aus dem Repository ausliest und mit den richtigen Kategorienamen anreichert war recht lang und nicht gerade einfach zu verstehen.
Der Name der Methode vermittelte auch nicht wirklich, dass die Daten noch angereichert werden.
Hier der alte Code:
#+begin_src java
public Set<LinkDto> getLinks() {
return linkRepository
.getAll()
.stream()
.map(link ->
new LinkDto(link.getCreator(),
link.getUrl(),
link
.getCategoryIds()
.stream()
.map(categoryRepository::getById)
.map(optional -> optional.orElseThrow(() ->
new CategroyDoesNotExist(
"A Category for a certain id does not exits.
You must create it first.")))
.collect(Collectors.toSet()),
link.getTags()))
.collect(Collectors.toSet());
}
#+end_src
Das Aufteilen in kleiner Funktionen machte den Code besser lesbar und die zusätzlich eingefügten Funktionsnamen machen deutlicher, was genau der Code macht.
Es wurde Extract-Method genutzt.
#+begin_src java
public Set<LinkDto> getLinks() {
return linkRepository.getAll()
.stream().map(convertLink())
.collect(Collectors.toSet());
}
private Function<Link, LinkDto> convertLink() {
return link -> new LinkDto(
link.getCreator(),
link.getUrl(),
getCategoriesOf(link),
link.getTags());
}
private Set<Category> getCategoriesOf(Link link) {
return link.getCategoryIds()
.stream().map(getCategoryForId())
.collect(Collectors.toSet());
}
private Function<CategoryId, Category> getCategoryForId() {
return id -> categoryRepository
.getById(id)
.orElseThrow(() ->
new CategroyDoesNotExist(
"A Category for a certain id does not exits.
You must create it first."));
}
#+end_src
Die Änderung wurde mit Commit [[https://tea.filefighter.de/qvalentin/LinkDitch/commit/a03206a8e1][a03206a8e1]] durchgeführt.
*** 2 Refactorings
:PROPERTIES:
:CUSTOM_ID: refactorings
@ -809,6 +930,54 @@ Das Refactoring wurde mit Commit [[https://tea.filefighter.de/qvalentin/LinkDitc
[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen,
sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]
**** Replace Error Code with Exception
Der Code zum überprüfen, ob eine URL auf eine aktuell erreichbare Ressource zeigt gab im Falle einer IO Exception den ErrorCode 500 (anhand des HTTP Status Codes) zurück.
Somit konnte ein Internal Server Error der angefragten Ressource nicht von einem lokalen Fehler, wie fehlender Netzwerkverbindung oder einem DNS-Fehler unterschieden werden.
Deutlich besser ist es, direkt eine passende Exeption zu werfen und dabei die Informationen an den Nutzer weiterzuleiten, sowie die möglichen IOExceptions zu unterscheiden und einen Sonderfall für einen DNS Error, der recht wahrscheinlich ist einzuführen.
Alter Code:
#+begin_src java
private static int getResponseCode(LinkUrl url) {
try {
HttpURLConnection http = (HttpURLConnection) url.getUrl()
.openConnection();
http.setRequestMethod("HEAD");
http.disconnect();
return http.getResponseCode();
}
catch (IOException e) {
return 500; //TODO: seems smelly
}
}
#+end_src
Alter Code:
#+begin_src java
private static int getResponseCode(LinkUrl url) {
try {
HttpURLConnection http = (HttpURLConnection) url.getUrl()
.openConnection();
http.setRequestMethod("HEAD");
http.disconnect();
return http.getResponseCode();
}
catch (UnknownHostException unknownHostException){
throw new URLIsNotReachable("The host of the url " + url + " could not be resolved.");
}
catch (IOException e) {
throw new URLIsNotReachable(
"Something went wrong when trying to check if the url " +
url +
" is reachable. Make sure your internet connection is working: "
+ e.getMessage());
}
}
#+end_src
Das Refactoring wurde mit Commit [[https://tea.filefighter.de/qvalentin/LinkDitch/commit/7fbc3f722c][7fbc3f722c]] durchgeführt.
* Kapitel 8: Entwurfsmuster
:PROPERTIES:
:CUSTOM_ID: kapitel-8-entwurfsmuster

Binary file not shown.

BIN
Documentation/uml/Link.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 35 KiB