
Inhalt
Dieser Blogartikel ist etwas länger geworden - insofern hier für die es heute nicht mehr gewohnten Augen eine Kleine Übersicht der Kapitel.
Worum geht's?
Ein bisschen TechStack
Herausforderungen
Beispiele
Code
Fazit
Worum geht's?
Als ein Team mit verschiedenen Erfahrungen und Vorlieben war uns von Anfang an klar, dass ein jeder am liebsten auf "seinem" OS Arbeiten und leben möchte.
Wir hatten also die Wahl: Entweder ein einheitliches Setup schaffen, durch das für alle Beteiligten die Entwicklung identisch wird. Oder wir finden ein OS, mit dem alle gleichermaßen zufrieden sind. (bzw. auf das ein Teil des Teams einfach gezwungen wird.)
Wir entschieden uns dafür ein gemeinsames Setup zu nutzen, völlig unabhängig der persönlichen Vorlieben. Unser Anspruch sah vor den Zugang für alle Entwickler gleichermaßen zu vereinfachen, sodass keine Voraussetzungen an Erfahrungen und Skills gesetzt wurden.
Wir alle wussten von unseren vorherigen Stationen, wie divers Teams sein können und wie unterschiedlich jeder Entwickler arbeitet. Deshalb räumten wir der Thematik eine hohe Priorität ein und versuchten Einiges, um ein möglichst gutes und für uns Entwickler homogenes Ergebnis zu erhalten.
User TechStack
OS
Aus der Unix-Welt nehmen wir einiges mit, theoretisch wäre auch Windows möglich. Aktuell nutzt keiner von uns dies zum Arbeiten. Vielleicht findet sich jemand mit Spaß am Ausprobieren? Man hört ja so einiges (Gutes) über WSL.
- Arch mit GNOME
- macOS 11
- Arch Manjaro
- Ubuntu 20.10 LTS
Backend
Als Pimcore-Agentur ist in jedem Projekt auf jeden Fall schon einmal das Backend weitestgehend definiert. Allerdings haben wir durch die Bandbreite an unterschiedlichen Projekten (alte, wie neue) auch mittlerweile eine ganze Reihe an verschiedenen Stacks beisammen.
- PHP 7.4|8.1
- Pimcore 6.6|6.9|10.3
- Symfony 4.4|5.6
Frontend
Das Thema Frontend wird noch einen eigenen Artikel erhalten, aber soviel sei schon einmal gesagt: Unsere Frontends sind recht divers aufgestellt - immer ausgewählt nach den jeweiligen Kundenanforderungen.
- Vue2|3
- AlpineJS
- Webpack
- Reines Twig
Sprüche, die man bei uns NICHT hören wird
Aber bei mir ging das eben noch!
Ich will nicht in das andere Projekt gucken, da mir das Aufsetzen zu lange dauert.
Ich kann an dem Ticket nicht arbeiten, da ich keine/n Testdaten/content habe.
[FE] Wie geht das nochmal mit dem Projekt (Backend) aufsetzen?
[BE] Mein Browser findet keine Assets.
Wie kann ich nochmal die Seite lokal aufrufen?
Herausforderungen
Es gab im Prozess einige Probleme zu überwinden. Dabei definierten sie bei jeder internen Diskussion einen wichtigen Eckstein, der uns zeigte wo wir hin wollten bzw. was wir in der jeweiligen Situation vermeiden sollten.
Schnelles Aufsetzen von Projekten
Der Klassiker - ein Kunde oder Kollege bittet kurz einmal darum zur Unterstützung in ein Projekt zu schauen. Nicht ganz unbekannt ist, dass so etwas je nach Projekt schon einmal einen halben bis ganzen Tag dauern kann.
Unser eigener Anspruch war hier, dass wir nach dem Klonen des git repositorys innerhalb von 5-10 Minuten auch bereits arbeitsfähig sind.
Kompatibilität der eingesetzten Systeme
Wie schon oben erwähnt, nutzen wir nicht alle die gleichen Systeme zum Arbeiten. Wichtig war uns hier nicht nur, dass alle Kollegen gleichermaßen arbeiten können, sondern auch, dass für alle die gleichen Regeln und Prozesse gelten. Das heißt in der Umsetzung, dass wir uns alle gegenseitig sehr stark unterstützen können. Auch dann, wenn der Mac-User keine Ahnung von Arch und vice versa hat.
Voraussetzungsloser Einstieg
Die Hürde mit unserem Setup zu arbeiten sollte für alle, unabhängig des persönlichen Erfahrungsschatzes, gleichermaßen niedrig sein. So kam es, dass alles Enwicklungsbezogene durch ein paar universelle Befehle vollziehbar ist.
Möglichkeit zum parallelen Arbeiten
Ein weiterer Klassiker - Entwickler können nur eine kleine Anzahl an Projekten gleichzeitig aufgesetzt haben und aktiv laufen lassen, da die Ressourcen der Maschinen nicht ausreichend sind.
Bei uns kann ein Backend-Entwickler lokal alle Projekte gleichzeitig laufen lassen und daran arbeiten, solange der RAM die vielen Instanzen der eigenen IDE überlebt. D.h. auf Deutsch: Ressourcenarmes Betreiben der eingesetzten Services.
Beispiele
Hier sind ein paar Beispiele, bevor wir uns in den Code begeben, die verdeutlichen sollen, wie einfach es für uns alle nun ist. Folgende Beispiele kommen quasi jeden Tag vor und gehen flott von der Hand.
Fallen Euch hier noch weitere ein?
Neues Projekt aufsetzen
Kurz den link aus dem Repo suchen, klonen und fast fertig. Je nach Maschine kann der init etwas dauern, da hier auch direkt die Assets für das FE mit gebaut werden.
git clone xy cd xy make init
Projekt starten
Wenn der (Mutagen) Sync noch "frisch" ist dauert es 5 Sekunden. Falls nicht, so können es 2 min sein.
make start
Zerstörtes Projekt neu aufsetzen
Jeder Entwickler kennt es: Irgendetwas hat man gerade versucht und dann bekommt man das System / das Projekt nicht mehr gefixt. Nun könnte man stundenlang mit der Suche nach dem Fehler verbringen - oder einfach das zerlegte Projekt dezent beseitigen, neu aufsetzen und dort weitermachen, wo bis eben nichts funktionierte.
make clear -v
(wenn ganz schlimm: docker service neustarten: systemctl restart docker.service)
cd xy rm -Rf xy git clone xy cd xy make init
Projekt stoppen
Auch wir gehen mal ins Bett und räumen hinter uns auf.
make clear
Sprüche, die man sehr wohl bei uns hört
Shit, schon wieder lokal und Stage verwechselt! (Die Systeme sind bei uns zum Verwechseln ähnlich)
Hast du mal make init gemacht?
Pull' mal, ich habe dir einen Service gebaut.
Code
docker-compose.yaml
In der YAML wird ein ganzes Projekt für die lokale Entwicklung (backendseitig) komplett durch dekliniert. Neben den Standards, wie der Datenbank (maria), dem front-facing webserver (nginx) und dem Pimcore Container (php8.1-fpm) haben wir noch einen zweiten debug-container für die harten Fälle und optionale Komponenten. Aktuell setzen wir hier ein: redis, athena-pdf, elastic. Je nach Projektbedarf kann hier einfach hinzugefügt werden und tiefgreifender konfiguriert.
version: '3.7'
services:
redis:
image: redis:alpine
labels:
- "traefik.enable=false"
networks:
- internal
db:
image: mariadb:10.5
working_dir: /application
command: [ mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb-large-prefix=1, --innodb-file-per-table=1 ]
volumes:
- db:/var/lib/mysql
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
env_file:
- .env.local
networks:
- internal
labels:
- "traefik.enable=false"
nginx:
image: nginx:stable-alpine
working_dir: /var/www/html
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_network"
- "traefik.http.routers.${PROJECT_NAME}.rule=Host(`${PROJECT_NAME}.lntc`)"
depends_on:
- pimcore
- pimcore-debug
volumes:
- pimcore:/var/www/html:ro
- ./.docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- internal
- traefik_network
pimcore: &pimcore
user: "${USER_ID}:${GROUP_ID}"
build:
context: .
dockerfile: .docker/Dockerfile.pimcore
environment: &pimcore-environment
APACHE_DOCUMENT_ROOT: /var/www/html/public
COMPOSER_HOME: /var/www/html
env_file:
- .env.local
labels:
- "traefik.enable=false"
depends_on:
- db
- redis
- athena
volumes:
- pimcore:/var/www/html:cached
networks:
- internal
pimcore-debug:
<<: *pimcore
build:
context: .
dockerfile: .docker/Dockerfile.pimcore-debug
environment:
<<: *pimcore-environment
PHP_IDE_CONFIG: "serverName=${PROJECT_NAME}.lntc"
athena:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_network"
- "traefik.http.routers.${PROJECT_NAME}-athena.rule=Host(`athena.${PROJECT_NAME}.lntc`)"
- "traefik.port=8080"
image: arachnysdocker/athenapdf-service:3
environment:
WEAVER_AUTH_KEY: "leanatic_secure_password_not123"
networks:
- traefik_network
elastic:
labels:
- "traefik.enable=false"
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
environment:
- node.name=elastic
- cluster.name=${PROJECT_NAME}-docker-cluster
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elastic:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- internal
volumes:
db:
pimcore:
networks:
traefik_network:
external: true
internal:
x-mutagen:
sync:
defaults:
symlink:
mode: "posix-raw"
ignore:
vcs: true
pimcore:
alpha: "./"
beta: "volume://pimcore"
mode: "two-way-resolved"
permissions:
defaultOwner: "id:${USER_ID}"
defaultGroup: "id:${GROUP_ID}"
defaultFileMode: 0644
defaultDirectoryMode: 0755
ignore:
paths:
- "/symfony-cache/"
Wie schon erwähnt nutzen wir træfik für das Routing. Dies könnte in Zukunft noch ein eigener Artikel werden. Hier gibt es noch ein wenig zu beachten bei der Installation und Einrichtung.
Im letzten Teil definieren wir neben den Volumes und dem einen geteilten Netzwerk für træfik den Mutagen-sync. Das ist der eine Teil, welches es uns erlaubt komplett gleichartig auf allen Unix-oiden zu arbeiten. Ansonsten hatten (und hätten) wir Probleme mit der Docker-synchronisation auf dem Mac (auch nicht bei allen, wie es scheint).
Makefile
Wie schon erwähnt ist das Makefile unser Arbeitspferd in der täglichen Entwicklung. Es nimmt gerade viele kleine (auf die Dauer sehr anstrengende) Themen ab und reduziert die Probleme miteinander.
So haben wir nur eine überschaubare Anzahl an Befehlen, welche alles backendseitige Aufzetzen und Warten entweder komplett abnehmen (für die FE'ler), oder zumindest stark vereinfachen für alle BE'ler.
# Making sure we are having our variables from .env
include .env
include .env.local
export
USER_ID = $(shell id -u)
GROUP_ID = $(shell id -g)
# Other variables
PIMCORE_DIR = .
PIMCORE_SNAPSHOT_DIR = $(PIMCORE_DIR)/etc/snapshot
# The base command to start the docker setup.
DOCKER_COMPOSE = GROUP_ID=$(GROUP_ID) USER_ID=$(USER_ID) mutagen-compose
#############################################################################################
######################################### Utilities #########################################
#############################################################################################
.PHONY: start
start:
# Starting mutagen daemon if not already up
mutagen daemon start
# Staring Docker Compose with mutagen
$(DOCKER_COMPOSE) up -d
.PHONY: stop
stop:
# Killing mutagen compose
$(DOCKER_COMPOSE) stop
.PHONY: clear
clear:
# Killing mutagen compose
$(DOCKER_COMPOSE) down
.PHONY: remove_volumes
remove_volumes:
# Killing mutagen compose
$(DOCKER_COMPOSE) down -v
.PHONY: init
init: initialize
.PHONY: initialize
initialize:
$(MAKE) start
$(MAKE) fix_permissions
$(MAKE) pimcore_assets
$(MAKE) pimcore_packages
$(MAKE) pimcore_restore
$(MAKE) pimcore_classes_rebuild
$(MAKE) pimcore_clear_cache
# Mac users tend to have an issue without
.PHONY: fix_permissions
fix_permissions:
$(DOCKER_COMPOSE) exec --user root pimcore chown -R $(USER_ID):$(GROUP_ID) /var/www/
.PHONY: pimcore_assets
pimcore_assets:
# Build Symfony encore assets
cd $(PIMCORE_DIR) && yarn install && yarn build
.PHONY: pimcore_packages
pimcore_packages:
# Run composer install inside the pimcore container
$(DOCKER_COMPOSE) exec pimcore composer --no-ansi --no-interaction install
# Just a convenience alias
.PHONY: snapshot
snapshot: pimcore_snapshot
.PHONY: pimcore_snapshot
pimcore_snapshot: _create_pimcore_snapshot_folders
# Save assets via zip
zip -r -v $(PIMCORE_SNAPSHOT_DIR)/public/var/assets/assets.zip $(PIMCORE_DIR)/public/var/assets/
# Save database
# Removing all the definers - which would break on importing
$(DOCKER_COMPOSE) exec db mysqldump --routines --add-drop-table -u root $(MYSQL_DATABASE) | grep -v 'SQL SECURITY DEFINER' | sed -e 's/DEFINER[ ]*=[ ]*[^*]*\*/\*/' | sed -e 's/DEFINER[ ]*=[ ]*[^*]*PROCEDURE/PROCEDURE/' | sed -e 's/DEFINER[ ]*=[ ]*[^*]*FUNCTION/FUNCTION/' > $(PIMCORE_SNAPSHOT_DIR)/dump.sql
# Just a convenience alias
.PHONY: restore
restore: pimcore_restore
.PHONY: pimcore_restore
pimcore_restore: _create_pimcore_snapshot_folders
# Clear assets to make sure we do not add unused assets when we take a snapshot again
rm -rf $(PIMCORE_DIR)/public/var/assets
# Restore assets via zip
unzip -o $(PIMCORE_SNAPSHOT_DIR)/public/var/assets/assets.zip
# Restore the database
$(DOCKER_COMPOSE) exec -T db mysql -u root $(MYSQL_DATABASE) < $(PIMCORE_SNAPSHOT_DIR)/dump.sql
.PHONY: _create_pimcore_snapshot_folders
_create_pimcore_snapshot_folders:
mkdir -p $(PIMCORE_SNAPSHOT_DIR)/public/var/assets
mkdir -p $(PIMCORE_DIR)/public/var/assets
.PHONY: pimcore_classes_rebuild
pimcore_classes_rebuild:
# Rebuilding classes
$(DOCKER_COMPOSE) exec pimcore bin/console pimcore:deployment:classes-rebuild --no-interaction --create-classes -v
.PHONY: pimcore_clear_cache
pimcore_clear_cache:
# Killing cache
$(DOCKER_COMPOSE) exec pimcore bin/console cache:clear --no-interaction -v
$(DOCKER_COMPOSE) exec pimcore bin/console pimcore:cache:clear --no-interaction -v
.PHONY: pimcore_check_var_dump
pimcore_check_var_dump:
# Killing cache
$(DOCKER_COMPOSE) exec pimcore vendor/bin/var-dump-check src/ --symfony
.PHONY: pimcore_console
pimcore_console: console
.PHONY: console
console:
docker-compose exec pimcore bin/console $(filter-out $@,$(MAKECMDGOALS))
Vielleicht fiel auf, dass wir eine gemeinsame Entwicklungsdatenbank haben, welche auch immer mit ins Repo eingechecked wird. Somit haben wir für alle nicht Admin, oder Backend-bezogenen auch direkt einen fertigen (funktionierenden) Stand mit Zugangsdaten und notwendigen Datenobjekten (Usern etc.).
Dies führt auch dazu, dass wir sehr schnell einen Stand löschen und komplett neu aufsetzen können, sollte man mal wieder (was ja mal vorkommt) seine Instanz komplett zerschossen haben.
Fazit
Wir haben mit einigem Trial-and-Error eine Lösung gefunden, sodass alle unsere beteiligten Gewerke einfach und schnell miteinander, jeder an seinen Themen, arbeiten können. Weiter haben wir es geschafft uns zu öffnen für viele Möglichkeiten ohne potentielle neue Mitglieder unseres Teams einzuschränken.
Unser Onboarding wird damit massiv vereinfacht und verkürzt.
