Moin,
neben den bereits anderen guten Antworten möchte ich ein paar technische und methodologisch Anreize und Tools in den Raum werfen )ohne dabei zu sehr auf deine erwaehnte Probleme einzugehen) - ausgehend von deinem Java-Background und der Aussage, dass du dich gerne weiter entwickeln moechtest.
Ich entwickle seit einigen Jahren ausschließlich testgetrieben. Testgetrieben zu entwickeln bedeutet den Code so zu implementieren, dass er einfacher testbar ist. Allein durch diese Vorgehensweise folgt man recht intuitiv bekannten Pattern und Prinzipien und erhoeht Qualitaet und Wartbarkeit.
SOLID
Beispiele:
a) Code under test darf keine neue Instanzen erstellen oder auf static Methoden zugreifen, da diese nicht (lies: nur sehr schlecht) testbar sind. Spring-Boot liefert hierzu beste Voraussetzungen. Dependencies werden injected, können in den Tests als Mock übergeben werden. --> (Teile von) Inversion of control werden erfüllt.
b) Angenommen man hat einen Converter-Service, der durch eine beliebige Liste von Converter iteriert und Daten von x nach y converted:
if(myConverter.class == XMLConverter)
myConverter.convertToXml()
else if(myConverter.class == PDFConverter)
myConverter.convertToPDF()
Dies ist recht aufwendig zu testen, und bei jeder Erweiterung muss der Service modifiziert werden. --> Testgetrieben fällt einem bereits vor der Implementierung auf, dass das Open-close-principle nicht eingehalten wird: Open for extension, close for modification. OCP folgend wird der Code in etwa so aussehen, da alle Converter einem Interface folgen.
myConverters.foreach {
it.convert()
}
c) Da fuer eine Unit sehr haeufig drei/vier oder mehr Tests (plus Negativtest(s)) erstellt werden, merkt man beim Testen bereits, dass eine Klasse eventuell zu viele Aufgaben hat. Spaetestens wenn dann noch n Services gemockt werden muessen, refactored man die unit under test in mehrere Units. --> single-responsibility, seperation of concerns werden eingehalten.
Ich koennte hier noch recht viele Beispiele nennen, aber ich denke du weiszt worauf ich hinaus moechte..
Lesestoff:
- https://zeroturnaround.com/rebellabs/object-oriented-design-principles-and-the-5-ways-of-creating-solid-applications
- https://dzone.com/articles/the-solid-principles-in-real-life
- https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
Testing Tools
Testgetrieben zu entwickeln bedeutet, dass man den Spring-Container haeufig lediglich dazu starten muss, dass der Test ausgefuehrt werden kann. Da das ein paar Sekunden in Anspruch nimmt und ein Entwickler ungerne wartet, empfiehlt es sich sehr dem Prinzip der Testing-Pyramide zu folgen:
- Viele Unit-Tests
- Einige Integration-Tests
- Wenige Functional-(E2E)-Tests
Spring-Boot bietet allerdings an, fuer Integration-Tests nicht den gesamten Container starten zu muessen.
Das heisst du kannst auf die Annotation
@RunWith(SpringRunner.class)
verzichten, und dennoch wird der ServletContext initialisiert, sprich Routes werden erstellt, Annotations interpretiert, Beans geladen etc..
Beispiel Request innerhalb eines Tests:
MockHttpServletResponse response = this.mockMvc.perform(
MockMvcRequestBuilders
.get(url)
.accept(MediaType.APPLICATION_JSON))
.andReturn()
.response
Ich persönlich nutze (wenn ich die Auswahl habe) in fast allen Projekten das Spock-Framework. Dies ist ein Testing-Framework basierend auf Groovy. Beispiel:
def 'getById: convert businessAreaEntity to DTO and return it'() {
given:
businessAreaServiceMock.findById(_, _) >> ANY_BUSINESS_AREA_ENTITY_1
when:
BusinessAreaDTO result = underTest.getById(AUTH_USER, ANY_BUSINESS_AREA_ID_1)
then:
1 * objectConverterMock.createDto(ANY_BUSINESS_AREA_ENTITY_1) >> ANY_BUSINESS_AREA_DTO_1
result == ANY_BUSINESS_AREA_DTO_1
}
Durch die benannten Methoden und given-when-then-Syntax sind die Tests sehr einfach zu lesen und zu verstehen.
Fuer das Mocking von 3rd-Party-Diensten (oder generell Requests, die nach "drauszen" gehen) kann ich Wiremock empfehlen. Hierueber kann man ausgehende Request abfangen und beliebige Responses emulieren.
Lesestoff:
- https://martinfowler.com/bliki/TestPyramid.html
- https://thepracticaldeveloper.com/2017/07/31/guide-spring-boot-controller-tests/
- http://spockframework.org/
- http://wiremock.org/docs/getting-started/
- https://dzone.com/articles/spring-4-groovy
Module Design
- Wie Definiere ich welche Module sinnvoll wären? Und Kapsle ich den Code per Api-Server-Client Architektur oder macht das wenig Sinn, weil ich die ganze Anwendung besser als ein Module definieren sollte? Je mehr Module ich hab desto beschissener wird es den Code zu implementieren, weil ich dann Kreuz und Quer durch die Module klicken muss... andererseits wäre es besser die richtigen Module sofoert anzulegen als später per Refactoring zu modularisieren... könnt ihr mir hierzu ein paar Tipps geben?
Stateless RESTful-JSON-APIs auf Microservice-Basis sind so ziemlich Standard in der heutigen Web-Welt. Das Trennen von Frontend-Logik und Backend auch. Wegen der von dir erwaehnten Gefahr bezgl. einer kreuz-und-queren-Kommunikation gibt es wohl definierte software designs. Je nachdem, fuer welche du dich entscheidest, klaeren sich diese Probleme eigentlich recht schnell.
Beispiel (vereinfacht) anhand DDD (domain-driven-design) Es ist klar definiert, wo welche Logik zu implementieren ist. Frontend im UI-Layer, Handling im Application-Layer, Business-Logik im Domain-Layer, Repositories (Persistence) im Infrastructure-Layer. Wird eine Domain zu grosz, wird sie in einzelne Module unterteilt und es greift das Aggregation pattern: Mehrere Module werden von einem root-Modul orchestriert.
Beispiel (vereinfacht) anhand SOA (service-oriented-architecture) Jedes Modul ist self-contained, also in sich selbst gekapselt, ist eine Blackbox nach auszen, und spiegelt eine eindeutige Business-Activity mit wohl-definiertem Output wieder. Die Kommunikation zwischen Modulen findet ueber Contracts statt. D.h. auch hier findet eine explizite Definition von Modulen statt.
Natuerlich gibt es noch einige andere Loesungsansaetze in der Software-Architektur.
Lesestoff:
- https://www.nginx.com/blog/refactoring-a-monolith-into-microservices/
- https://technologyconversations.com/2014/08/12/rest-api-with-json/
- https://www.infoq.com/minibooks/domain-driven-design-quickly
- https://en.wikipedia.org/wiki/Service-oriented_architecture
Monitoring
Wie auch immer man sich entscheidet, durch distributed services kommen ein paar Komplikation auf einen zu, vor allem wenn man mitbekommen moechte, was genau wo und wann geschieht.
Hier hat sich der ELK-Stack (Elasticsearch, Logstash, Kibana) sehr etabliert. (Neuerdings ist da noch Beat hinzugekommen).
Der Ablauf des ELK-Stacks kurz skizziert:
- Jeder Microservice loggt in sein eigenes Logfile.
- Filebeat pushed changes nach Logstash
- Logstash parsed anhand definierter Grok-Patterns die unterschiedlichen Logfiles und sendet sie an Elasticsearch
- Kibana liest die Daten aus Elasticsearc aus und visualisiert sie entsrepchend.
Lesestoff:
- https://logz.io/learn/complete-guide-elk-stack/
- https://www.elastic.co/guide/index.html
- https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
- https://www.elastic.co/products/kibana
- https://www.elastic.co/products/beats/filebeat
Continuous deployment, Containers
Auch zum mittlerweiligen Standard gehoert das kontinuierliche Deployment der Anwendungen. Hier gibt es diverse Anbieter - von kostenfrei bis zu Enterprise, name-dropping: Teamcity, Jenkins, Gitlab CI.
Lesestoff:
- https://www.jetbrains.com/teamcity/features/
- https://docs.gitlab.com/ce/ci/environments.html
- https://dzone.com/articles/jenkins-pipeline-for-continuous-delivery-and-deplo
Diverses
Da du ja sehr interessiert bist, neue Dinge zu lernen, moechte ich an dieser Stelle (kontextlos) noch Histrix, Docker und Kubernetes, so wie AWS serverless erwaehnen.
Cheers, markk