This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the workflows category.
Last Updated: 2024-12-03
I'm going to demonstrate with an example from a JavaScript codebase.
When I started working on Project M, I had no idea where to start and wasted a lot of time. Instead I should have went to their office and asked them to show me around the code and answer questions in real-time.
When there's a README or Wiki in place, great. But often READMEs will be non existent because knowledge is implicit.
In a JavaScript project, look at package.json
scripts
section:
"scripts": {
"build": "rimraf ./lib && tsc",
"postbuild": "pkg-ok",
"docs": "typedoc",
"lint": "tslint src/**/*.ts",
"release": "semantic-release",
"test": "yarn test:unit && yarn test:integration",
"test:unit": "jest -c ./jest.unit.config.js",
"test:integration": "jest -c ./jest.integration.config.js --runInBand",
"seed": "ts-node src/seeders/index.js"
},
From this, we now know how to seed the data, how to run integration tests, and
how to lint. I also saw some commands I was unfamiliar like typeorm
. Their
presence up here suggests they may be important parts of the architecture.
In other projects, there might be a /bin
or /scripts
folders that offer something similar.
Look at dependencies in package.json
to see any homegrown libraries they use.
"dependencies": {
"@middy/core": "^1.0.0-alpha.22",
"@middy/http-cors": "^1.0.0-alpha.22",
"@middy/ssm": "^1.0.0-beta.6",
"auth-helper": "bitbucket:Project M/auth-service#1.1.0",
"aws-sdk": "^2.532.0",
"axios": "^0.18.0",
"backend-logger": "bitbucket:Project M/logger-service#4.1.0",
...
}
Here we see that auth-service
and logger-service
both link to private
bitbucket repos belonging to the client, Project M
.
src
folderI saw both a lib
and a src
folder with almost identical code. What purposes
do these play? Grepping through the top-level files in the codebase led me to
a tsconfig.json
which basically says that the lib
is the outdir
for the
generated code. Thus src
must be the code we write. Makes sense based on the
terminology used.
{
"compilerOptions": {
...
"outDir": "lib"
},
"exclude": [
"node_modules",
"**/*.sdk.ts",
"**/sdk.ts",
"./lib"
]
}
In this JavaScript project, it was src/index.ts
, a file which exports entities to the
world outside the package.
From this starting point:
export * from './Database' // Me: "Now I need to look what is in ./Database/index.js" file
export * from './entities'
So here is ./entities/index.js
export { Consultation } from './Consultations/Consultation'
export { ConsultationMessage } from './Consultations/ConsultationMessage'
export { ConsultationTopic } from './Consultations/ConsultationTopic'
export { CustomerMessage } from './Consultations/CustomerMessage'
...
This gives you a good idea of the shape of actual data as well as clues as to the importance of different entities:
export const seedPurchases: Array<
RelationAsId<Purchase, 'customer' | 'accountant' | 'product'>
> = [
{
autoRenewal: true,
createdAt: new Date('2018-10-02T11:00:00.000Z'),
customer: { id: 1 },
id: 1,
ipAddress: '1.1.1.1',
accountant: { id: 1 },
paid: true,
paymentMethod: PaymentMethod.PayPal,
product: { id: 1 },
updatedAt: new Date('2018-10-02T11:00:00.000Z')
},
{
autoRenewal: true,
createdAt: new Date('2018-10-03T11:00:00.000Z'),
customer: { id: 2 },
id: 2,
ipAddress: '1.1.1.1',
accountant: { id: 1 },
paid: true,
paymentMethod: PaymentMethod.PayPal,
product: { id: 2 },
updatedAt: new Date('2018-10-03T11:00:00.000Z')
},
import { getRepository, Purchase } from '../..'
import { setupDatabase } from '../../test/setupDatabase'
describe('Purchase', () => {
setupDatabase()
it('reads the correct count of seed data', async () => {
expect(await getRepository(Purchase).count()).toEqual(4)
})
it('reads the first entry of seed data', async () => {
expect(await getRepository(Purchase).findOne(1)).toEqual({
autoRenewal: true,
createdAt: expect.any(Date),
id: 1,
ipAddress: '1.1.1.1',
paid: true,
paymentMethod: 'PayPal',
referredBy: null,
updatedAt: expect.any(Date)
})
})
})
This shows us that the getRepository
function takes an entity such as
Purchase
and can be called with count()
or findOne()
Setup and tear-down methods give clear hints about what is going on to get the ENV setup. We see how to seed here!
import { DatabaseEnvironment } from './databaseEnvironment'
export function setupDatabase (runSeeders?: boolean) {
let environment: DatabaseEnvironment
beforeAll(async () => {
environment = new DatabaseEnvironment({ runSeeders })
await environment.setup()
})
afterAll(async () => {
await environment.teardown()
})
}
If there are no high-level tests like this, then at least refer to normal tests.
In this project, the docker-compose.yml
file specifies much.
Here we see postgres is needed and we also get the passwords required for getting going. That's a good start.
version: "3"
services:
test-microservice-db:
image: postgres:11.5
volumes:
- ./db.tmp:/var/lib/pgsql/data:Z
ports:
- "5432:5432"
restart: always
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: G~UxaX7E
POSTGRES_DB: database
The circleci
setup for CI server setup does much too.
There was a huge amount of info here, but one part here reveals an implicit
expectation that the developer's computer have $AWS_ACCESS_KEY_ID
set:
createDocs:
docker:
- image: circleci/python:2.7
working_directory: ~/repo/
steps:
- attach_workspace:
at: .
- run:
name: Install AWS CLI
command: |
sudo pip install awscli
- run:
name: Create AWS credentials manually
command: |
mkdir ~/.aws
touch ~/.aws/config
chmod 600 ~/.aws/config
echo "[profile eb-cli]" > ~/.aws/config
echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
- run:
name: Copy documentation to S3
command: |
aws s3 sync ~/repo/docs s3://Project M-dev-docs/$CIRCLE_PROJECT_REPONAME --delete