Tuesday, 9 February 2021

Part 10: An article about moving files and changing configuration

G'day:

I hope I correctly set the excitement expectations with the title of this one. It's gonna be dead dull. In the previous article ("I mess up how I configure my Docker containers"), I detailed a fundamental flaw in how I was configuring my Dockerfiles and docker-compose.yml file, which pretty much had a logic-conflict in them. Instead of using the fullstack-exercise codebase I've been working on in this series, I used a cut down one that focused specifically on the issue. In this article I am detailing the file-system and config reorganisation I then performed on the fullstack-exercise codebase to fix the issue. TBH I'd probably not bother reading it if I was you (my fictitious reader), cos it's even more dry than my usual efforts. I'm pretty much only writing it out of a sense of completeistness (!), and also in case someone happens to be reading along with the rest of the series and - if they came to the next article - suddenly thought "hang on all the files have moved around? What subterfuge is this?". So it's a full-disclosure exercise I guess. If you do insist on reading this, read the previous article first though, eh? Good luck.

As per usual: I'll remind you that this is part 10 of an 11(?) part series, with the earlier articles linked below:

  1. Intro / Nginx
  2. PHP
  3. PHPUnit
  4. Tweaks I made to my Bash environment in my Docker containers
  5. MariaDB
  6. Installing Symfony
  7. Using Symfony
  8. Testing a simple web page built with Vue.js using Mocha, Chai and Puppeteer
  9. I mess up how I configure my Docker containers
  10. An article about moving files and changing configuration (this article)
  11. Setting up a Vue.js project and integrating some existing code into it
  12. Unit testing Vue.js components

The TL;DR of the previous article is kinda:

Don't map volumes in docker-compose.yml over the top of the working directory specified in Dockerfile, if the Dockerfile actually creates stuff you need in that working directory (like a node_modules subdirectory, for example). This is because a volume mapping replaces what's there, it does not merge with it.

Schoolkid dumbarsery from me there.

Now I'm gonna apply the lessons learned there to the main codeabse for this project. This is the directory structure I had previously (on github). Note some stuff not relevant to this exercise has been removed:

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise$ tree -F --dirsfirst -L 2
.
├── bin/
│   └── console*
├── config/
│   └── [… Symfony stuff …]
├── docker/ [… subdirectory contents elided for brevity …]
│   ├── mariadb/
│   ├── nginx/
│   ├── node/
│   ├── php-fpm/
│   └── docker-compose.yml*
├── public/
│   ├── button.html*
│   ├── gdayWorld.html*
│   ├── gdayWorld.php*
│   ├── gdayWorldViaVue.html*
│   ├── githubProfiles.html*
│   ├── index.php*
│   ├── invalidNotificationType.html*
│   └── notification.html*
├── src/
│   ├── Kernel.php*
│   └── MyClass.php*
├── tests/
│   ├── functional/
│   │   ├── public/
│   │   │   ├── ButtonTest.js*
│   │   │   ├── GdayWorldViaVueTest.js*
│   │   │   ├── GithubProfilesTest.js*
│   │   │   ├── NotificationTest.js*
│   │   │   ├── PhpTest.php*
│   │   │   └── WebServerTest.php*
│   │   └── SymfonyTest.php*
│   ├── integration/
│   │   └── DatabaseTest.php*
│   └── unit/
│       └── MyClassTest.php*
├── LICENSE*
├── README.md*
├── composer.json*
├── composer.lock*
├── package-lock.json*
├── package.json*
├── phpcs.xml.dist*
├── phpmd.xml*
├── phpunit.xml.dist*
└── symfony.lock*

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise$

The two points that make it most obvious that things are poorly-organised here are:

  • the root directory which has a mix of stuff intended for the PHP container and other stuff intended for the Node.js container.
  • And, similarly the tests subdirectory has a mix of back-end PHPUnit tests and front-end Mocha tests in the same substructure.

Where there's a mess or a mix of things intended for two different purposes in the same place, it's a flag that something's possibly not right. Now I will be honest and say that this decision originally was purposeful on my part. I'm not distinguishing between the front-end part of the app (Node.js, Vue and Mocha stuff), and the back-end running Symfony and PHP. The front-end stuff is the web site for this app; the back-end will be the web service to support the front-end. They are not two distinct apps in my view (or one way of looking at it). This is not to say they can't still be organised a bit more coherently than I have.

To separate my concerns, I've decided to move all the code-related stuff into one of backend or frontend subdirectories. First the backend directory (see backend on Github):

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise$ tree -F --dirsfirst
.
└── backend/
   ├── config/
   │   ├── packages/
   │   │   ├── prod/
   │   │   │   └── routing.yaml*
   │   │   ├── test/
   │   │   │   └── framework.yaml*
   │   │   ├── cache.yaml*
   │   │   ├── framework.yaml*
   │   │   └── routing.yaml*
   │   ├── routes/
   │   │   └── dev/
   │   │       └── framework.yaml*
   │   ├── bundles.php*
   │   ├── preload.php*
   │   ├── routes.yaml*
   │   └── services.yaml*
   ├── public/
   │   ├── test-coverage-report/
   │   ├── gdayWorld.html*
   │   ├── gdayWorld.php*
   │   └── index.php*
   ├── src/
   │   ├── Controller/
   │   │   └── GreetingsController.php*
   │   ├── Kernel.php*
   │   └── MyClass.php*
   └── tests/
       ├── functional/
       │   ├── public/
       │   │   ├── PhpTest.php*
       │   │   └── WebServerTest.php*
       │   └── SymfonyTest.php*
       ├── integration/
       │   └── DatabaseTest.php*
       ├── unit/
       │   └── MyClassTest.php*
       └── bootstrap.php*

In the backend subdirectory I have all the PHP / Symfony / PHPUnit stuff, plus a public directory that is purely for the back-end web root. And - below - the Docker Nginx config now has separate websites for back-end and front-end; and in the php-fpm section we now have all the PHP / Symfony config stuff moved out of the application root, and into its own specific root:

└── docker/
    ├── nginx/
    │   ├── sites/
    │   │   ├── backend.conf*
    │   │   └── frontend.conf*
    │   └── Dockerfile*
    ├── php-fpm/
    │   ├── app_root/
    │   │   ├── bin/
    │   │   │   └── console*
    │   │   ├── var/
    │   │   │   └── cache/
    │   │   ├── composer.json*
    │   │   ├── composer.lock*
    │   │   ├── phpcs.xml.dist*
    │   │   ├── phpmd.xml*
    │   │   ├── phpunit.xml.dist*
    │   │   └── symfony.lock*
    │   ├── root_home/
    │   ├── Dockerfile*
    │   └── phpunit-code-coverage-xdebug.ini*
    └── docker-compose.yml*

The key part of the Nginx configuration changes here is that the two sites now have distinct host names: fullstackexercise.backend (see backend.conf on Github) for the PHP-oriented stuff, and fullstackexercise.frontend (see frontend.config on Github) for the Vue- / Node-based stuff. Each website only serves the type of files appropriate for their purpose.

The Nginx Dockerfile (on Github) has not changed significantly, but now the PHP-FPM one (on Github) copies all the application-root stuff into the working directory, rather than docker-compose.yml file using a volume to do this:

WORKDIR  /usr/share/fullstackExercise/
COPY ./app_root/ /usr/share/fullstackExercise/

It's worth looking at the whole lot of the service definitions for these from docker-compose.yml:

services:
  nginx:
    build:
      context: ./nginx
    volumes:
      - ../frontend/public:/usr/share/nginx/html/frontend
      - ../backend/public:/usr/share/nginx/html/backend
      - ../log:/var/log
      - ./nginx/root_home:/root
    ports:
      - "80:80"
    stdin_open: true # docker run -i
    tty: true        # docker run -t
    networks:
      backend:
        aliases:
          - fullstackexercise.frontend
          - fullstackexercise.backend

  php-fpm:
    build:
      context: ./php-fpm
    environment:
      - DATABASE_ROOT_PASSWORD=${DATABASE_ROOT_PASSWORD}
    volumes:
      - ../backend/config:/usr/share/fullstackExercise/config
      - ../backend/public:/usr/share/fullstackExercise/public
      - ../backend/src:/usr/share/fullstackExercise/src
      - ../backend/tests:/usr/share/fullstackExercise/tests
      - ./php-fpm/root_home:/root
    stdin_open: true
    tty: true
    networks:
      - backend

For Nginx we are mapping-in two separate volumes into the html directory: as per above, one for the back-end site, one for the front-end site. These are then used as the webroots in the site configuration for each website. We are also setting an alias for each website. This is just so the other containers can access the websites too.

In the PHP block, we now have separate volumes for each of the code directories in the application route (note that the config sub-directory there is Symfony app config, not like the composer.json, phpunit.xml.dist etc stuff that has been copied to the application root by (spoilers) php-fpm/Dockerfile. And, yeah, now the Dockerfile (on Github) for the PHP stuff. The only significant line is this one:

COPY ./app_root/ /usr/share/fullstackExercise/

That copies all the config files the PHP components need to run into the application root. One downside of this is that I can't make on-the-fly changes to things like the PHPUnit config from within PHPStorm, I need to use vi in the container, test it, then copy it back to the host machine. But that stuff changes so seldom it's fine by me.

The changes on the front-end side of things is along the same lines. Here's the file structure (and on Github):

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise$ tree -F --dirsfirst
. # a lot of stuff has been removed for the sake of brevity
├── docker/
│   └── node/
│       ├── config/
│       │   ├── babel.config.js*
│       │   ├── package-lock.json*
│       │   ├── package.json*
│       │   └── vue.config.js*
│       └── Dockerfile*
└── frontend/
    ├── public/
    │   ├── assets/
    │   │   └── scripts/
    │   │       ├── button.js*
    │   │       ├── gdayWorldViaVue.js*
    │   │       ├── githubProfiles.js*
    │   │       └── notification.js*
    │   ├── button.html*
    │   ├── gdayWorld.html*
    │   ├── gdayWorldViaVue.html*
    │   ├── githubProfiles.html*
    │   ├── invalidNotificationType.html*
    │   └── notification.html*
    ├── src/
    └── test/
        └── functional/
            ├── ButtonTest.js*
            ├── GdayWorldViaVueTest.js*
            ├── GithubProfilesTest.js*
            └── NotificationTest.js*

And the relevant bit of the node/Dockerfile (on Github):

WORKDIR  /usr/share/fullstackExercise/
COPY config/* ./

And docker-compose.yml (on Github):

  node:
    build:
      context: ./node
    environment:
      - GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN}
    volumes:
      - ../frontend/public:/usr/share/fullstackExercise/public
      - ../frontend/src:/usr/share/fullstackExercise/src
      - ../frontend/test:/usr/share/fullstackExercise/test
      - ./node/root_home:/root
    stdin_open: true
    tty: true
    networks:
      backend:
        aliases:
          - vuejs.backend

Here we see how - same as with the PHP stuff - we copy the config files over in Dockerfile, and then map volumes for the code directories in docker-compose.yml.

That's pretty much it really. The good thing with all this is that because I have full test coverage of my code, and some functional and integration tests as well, I have testing for all the config and all the interactions between all the containers, so at any moment in time when I go to refactor something - because all this really is is an exercise in refactoring - at every step I can check that everything still works. Or spend time working out why something didn't work. But that safety net is always there.

OK. I promise the next article is actually gonna get around to looking at Vue.js components, testing thereof, and hopefully draw a line under this series. BTW if you read this article all the way to here, you're bloody weird. But well done ;-)

Righto.

--
Adam