├── .editorconfig
├── .gitattributes
├── .github
├── CODEOWNERS
├── pull_request_template.md
└── workflows
│ ├── build.yml
│ ├── publish.yml
│ └── set_version
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── exports
└── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── renovate.json5
├── script
└── generate_images.sh
├── settings.gradle.kts
└── src
├── main
└── kotlin
│ └── uk
│ └── gov
│ └── justice
│ └── hmpps
│ └── architecture
│ ├── App.kt
│ ├── DefinesDecisions.kt
│ ├── HMPPSSoftwareSystem.kt
│ ├── annotations
│ ├── APIDocs.kt
│ ├── Capability.kt
│ ├── Notifier.kt
│ ├── OutsideHMPPS.kt
│ ├── ProblemArea.kt
│ ├── Tags.kt
│ ├── addTo.kt
│ └── delivers.kt
│ ├── defineDocumentation.kt
│ ├── defineGlobalViews.kt
│ ├── defineModelWithDeprecatedSyntax.kt
│ ├── defineStyles.kt
│ ├── defineWorkspace.kt
│ ├── documentation
│ ├── codeTracker.kt
│ └── versionTracker.kt
│ ├── export
│ └── backstage
│ │ ├── BackstageExporter.kt
│ │ ├── apispec
│ │ └── OpenApiSpecFinder.kt
│ │ └── model
│ │ ├── BackstageAPI.kt
│ │ ├── BackstageComponent.kt
│ │ ├── BackstageLifecycle.kt
│ │ ├── BackstageMetadata.kt
│ │ ├── BackstageModel.kt
│ │ └── BackstageSystem.kt
│ ├── git.kt
│ ├── model
│ ├── AWS.kt
│ ├── AdjudicationsApi.kt
│ ├── AnalyticalPlatform.kt
│ ├── AssessRisksAndNeeds.kt
│ ├── Azure.kt
│ ├── AzureADTenantJusticeUK.kt
│ ├── BookVideoLink.kt
│ ├── CRCSystem.kt
│ ├── CaseNotesToProbation.kt
│ ├── CloudPlatform.kt
│ ├── ComplexityOfNeed.kt
│ ├── ConsiderARecall.kt
│ ├── CourtRegister.kt
│ ├── CourtUsers.kt
│ ├── CreateAndVaryALicence.kt
│ ├── Curious.kt
│ ├── Delius.kt
│ ├── DigitalPrisonServices.kt
│ ├── DigitalPrisonsNetwork.kt
│ ├── EPF.kt
│ ├── EQuiP.kt
│ ├── HMPPSAPI.kt
│ ├── HMPPSAudit.kt
│ ├── HMPPSAuth.kt
│ ├── HMPPSDomainEvents.kt
│ ├── Heroku.kt
│ ├── IM.kt
│ ├── InterventionTeams.kt
│ ├── Interventions.kt
│ ├── KeyworkerApi.kt
│ ├── Licences.kt
│ ├── ManageASupervision.kt
│ ├── ManagePOMCases.kt
│ ├── MoJSignOn.kt
│ ├── NDH.kt
│ ├── NID.kt
│ ├── NOMIS.kt
│ ├── NationalPrisonRadio.kt
│ ├── Notify.kt
│ ├── OASys.kt
│ ├── Pathfinder.kt
│ ├── PolicyTeams.kt
│ ├── PrepareCaseForSentence.kt
│ ├── PrisonRegister.kt
│ ├── PrisonToProbationUpdate.kt
│ ├── PrisonVisitsBooking.kt
│ ├── PrisonerContentHub.kt
│ ├── PrisonerMoney.kt
│ ├── ProbationAllocationTool.kt
│ ├── ProbationCaseSampler.kt
│ ├── ProbationPractitioners.kt
│ ├── ProbationTeamsService.kt
│ ├── Reporting.kt
│ ├── RestrictedPatientsApi.kt
│ ├── StaffLookupApi.kt
│ ├── TierService.kt
│ ├── TokenVerificationApi.kt
│ ├── UnpaidWorkService.kt
│ ├── UseOfForce.kt
│ ├── UserPreferenceApi.kt
│ ├── WMT.kt
│ └── WhereaboutsApi.kt
│ └── views
│ ├── HMPPSDataAPI.kt
│ ├── HMPPSDomainAPI.kt
│ ├── HMPPSInternalAPI.kt
│ └── HMPPSView.kt
└── test
├── kotlin
└── uk
│ └── gov
│ └── justice
│ └── hmpps
│ └── architecture
│ └── export
│ └── backstage
│ └── BackstageExporterTest.kt
└── resources
├── backstage.yaml
└── structurizr.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig https://EditorConfig.org
2 |
3 | # this is the top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 |
10 | # Unix-style newlines with a newline ending every file
11 | end_of_line = lf
12 | insert_final_newline = true
13 |
14 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @ministryofjustice/hmpps-technical-architects
2 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## What does this pull request do?
2 |
3 | _Required._
4 |
5 | ## What is the intent behind these changes?
6 |
7 | _Required._
8 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and validate
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | - uses: actions/setup-java@v4
11 | with:
12 | distribution: 'adopt'
13 | java-version: '17'
14 | - run: |
15 | sudo apt-get -y install graphviz
16 | ./gradlew build
17 | - name: Test code by creating local workspace file
18 | run: ./gradlew run
19 | lint:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: actions/setup-java@v4
24 | with:
25 | distribution: 'adopt'
26 | java-version: '11'
27 | - name: Install ktlint
28 | run: |
29 | curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.41.0/ktlint
30 | chmod a+x ktlint
31 | - run: ./ktlint --editorconfig=.editorconfig "src/**/*.kt"
32 | test-image-generation:
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/checkout@v4
36 | - uses: actions/setup-java@v4
37 | with:
38 | distribution: 'adopt'
39 | java-version: '17'
40 | - uses: ts-graphviz/setup-graphviz@v2
41 | - run: script/generate_images.sh
42 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | schedule:
5 | - cron: "0 3 * * *"
6 | push:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | publish:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-java@v4
17 | with:
18 | distribution: 'adopt'
19 | java-version: '17'
20 | - run: |
21 | sudo apt-get -y install graphviz
22 | ./gradlew build
23 | - name: Push to workspace
24 | env:
25 | STRUCTURIZR_API_KEY: ${{ secrets.STRUCTURIZR_API_KEY }}
26 | STRUCTURIZR_API_SECRET: ${{ secrets.STRUCTURIZR_API_SECRET }}
27 | run: .github/workflows/set_version ./gradlew run --args='--push --backstage'
28 | - uses: actions/checkout@v4
29 | with:
30 | ref: backstage-export
31 | path: backstage
32 | - name: Publish backstage catalog
33 | run: |
34 | cp ./exports/backstage-*.yaml backstage/exports/
35 | cd backstage
36 | git config --global user.name "${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>"
37 | git config --global user.email "<${{ github.actor }}@users.noreply.github.com>"
38 | git add exports/backstage-*.yaml
39 | git commit -m "backstage catalog export at $(date)" --allow-empty
40 | git push
41 |
--------------------------------------------------------------------------------
/.github/workflows/set_version:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 | DATE="$(date '+%Y-%m-%d')"
3 | SHORT_SHA1=$(echo "$GITHUB_SHA" | cut -c1-7)
4 | export BUILD_VERSION="$DATE.$GITHUB_RUN_NUMBER.$SHORT_SHA1@$GITHUB_REPOSITORY"
5 | echo "Using BUILD_VERSION: ${BUILD_VERSION}"
6 | exec "$@"
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Gradle project-specific cache directory
2 | .gradle
3 |
4 | # Ignore Gradle build output directory
5 | build
6 |
7 | # external dependencies
8 | ext/
9 |
10 | # Ignoring IDE files
11 | .idea/
12 |
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Crown Copyright (c) 2020 Ministry of Justice
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |
4 | # hmpps-architecture-as-code
5 |
6 | Modelling architecture in HM Prisons and Probations Service (HMPPS) with the [C4 model][c4] and [Structurizr][structurizr].
7 |
8 | ## Key outputs
9 |
10 | - Holistic representation of all software systems and [containers][c4-abstractions] (applications or data stores) within HMPPS Digital
11 | - Applications annotated with API documentation links are published to the [Published APIs](https://structurizr.com/share/56937/documentation#%2F:Published%20APIs) page
12 | - Interactive graph of model elements and their relationships:
13 | - [Explore the system interactions](https://structurizr.com/share/56937/explore/graph?softwareSystems=true&view=)
14 | - [Explore the container (application) interactions](https://structurizr.com/share/56937/explore/graph?containers=true&view=)
15 | - [Explore how people use systems](https://structurizr.com/share/56937/explore/graph?people=true&softwareSystems=true&view=)
16 | - Views defined in the model are published to the [Diagrams](https://structurizr.com/share/56937/diagrams) page
17 | - For example, [overview of systems](https://structurizr.com/share/56937/images/system-overview.png) with [legend](https://structurizr.com/share/56937/images/system-overview-key.png)
18 | - Published diagram images can be embedded in Confluence or GitHub readmes
19 | - The diagrams will automatically update when they're next changed
20 | - Views defined in the model can be generated locally (see below)
21 |
22 | ## Workspaces
23 |
24 | This repository defines a Structurizr workspace for the HM Prison and Probation Service.
25 |
26 | For the model and diagrams, please visit https://structurizr.com/share/56937.
27 |
28 | [](https://structurizr.com/share/56937/diagrams#system-overview)
29 |
30 | 
31 |
32 | ## Linking from other repositories
33 |
34 | To link to a **live** version of a diagram, insert the following code into your repository's readme:
35 |
36 | (Replace `nomiscontainer` with the diagram key in the code)
37 |
38 | ```markdown
39 | [Container diagram source](https://github.com/ministryofjustice/hmpps-architecture-as-code/search?q=nomiscontainer)
40 |
41 | 
42 |
43 | 
44 | ```
45 |
46 | ## Running
47 |
48 | The project is built with `gradle`.
49 |
50 | | Action | Command |
51 | | --- | --- |
52 | | Build the project | `./gradlew build` |
53 | | Create a local Structurizr workspace JSON file | `./gradlew run` |
54 | | Push to the remote Structurizr workspace | `./gradlew run --args='--push'`
(please see **"Secrets"** section below) |
55 |
56 | ### :rotating_light: Remote-only changes will be lost
57 |
58 | The remote workspace's content is _replaced_ with the content in this repository. Remote-only changes will be **lost**.
59 |
60 | ### Secrets
61 |
62 | The `--push` command can be configured with these environment variables:
63 |
64 | | Environment variable | Meaning |
65 | | --- | --- |
66 | | `STRUCTURIZR_API_KEY` | **Required** The API key for the Structurizr API. |
67 | | `STRUCTURIZR_API_SECRET` | **Required** The API secret for the Structurizr API. |
68 | | `STRUCTURIZR_WORKSPACE_ID` | Overrides the default workspace ID. |
69 |
70 | Example:
71 | ```
72 | STRUCTURIZR_WORKSPACE_ID=12345 \
73 | STRUCTURIZR_API_KEY=key \
74 | STRUCTURIZR_API_SECRET=secret \
75 | ./gradlew run --args='--push'
76 | ```
77 |
78 | You can view these secrets on the [dashboard](https://structurizr.com/dashboard), after clicking *Show more...* next to
79 | the desired workspace.
80 |
81 | ## Generating images locally
82 |
83 | Requires `graphviz` and `wget` to be installed (e.g. with `brew install graphviz wget`)
84 |
85 | ```
86 | script/generate_images.sh
87 | ```
88 |
89 | This command will locally generate all defined workspace diagrams without using the Structurizr web API.
90 |
91 |
92 | [c4]: https://c4model.com/
93 | [c4-abstractions]: https://c4model.com/#Abstractions
94 | [structurizr]: https://structurizr.com/
95 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.1.0"
3 | application
4 | }
5 |
6 | application {
7 | mainClass.set("uk.gov.justice.hmpps.architecture.App")
8 | }
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | implementation(kotlin("stdlib"))
16 | implementation("com.structurizr:structurizr-client:1.29.0")
17 | implementation("com.structurizr:structurizr-core:1.29.0")
18 | implementation("com.structurizr:structurizr-import:1.7.0")
19 | implementation("com.structurizr:structurizr-graphviz:2.2.2")
20 | implementation("org.eclipse.jgit:org.eclipse.jgit:7.1.0.202411261347-r")
21 | implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2")
22 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
23 |
24 | testImplementation("org.assertj:assertj-core:3.27.3")
25 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.4")
26 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4")
27 | }
28 |
29 | val test by tasks.getting(Test::class) {
30 | useJUnitPlatform()
31 | }
32 |
--------------------------------------------------------------------------------
/exports/.gitignore:
--------------------------------------------------------------------------------
1 | # files saved by Structurizr API, CLI or scripts
2 | structurizr-*.json
3 | structurizr-*.puml
4 | structurizr-*.png
5 | *.dot
6 | *.svg
7 | *.cmapx
8 |
9 | # backstage export
10 | backstage-*.yaml
11 |
12 | # generated documentation
13 | docs/
14 |
15 | # cloned repositories
16 | clones/
17 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ministryofjustice/hmpps-architecture-as-code/c881326cbbe851b7ff431ae176708c4ade2b7ce7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["github>ministryofjustice/hmpps-renovate-config:base"],
4 | "packageRules": [
5 | {
6 | "matchManagers": ["gradle"],
7 | "matchUpdateTypes": ["patch", "minor"],
8 | "groupName": "all gradle dependencies",
9 | "schedule": ["before 7am on Monday"],
10 | },
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/script/generate_images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 | root_dir="$(git rev-parse --show-toplevel)"
3 | ext_dir="$root_dir/ext"
4 | exports_dir="$root_dir/exports"
5 | mkdir -p "$ext_dir"
6 |
7 | cli_version="2024.01.02"
8 | cli_zip="$ext_dir/structurizr-cli.zip"
9 | if [ ! -f "$cli_zip" ]; then
10 | echo
11 | echo "🔧 Downloading Structurizr CLI..."
12 | wget "https://github.com/structurizr/cli/releases/download/$cli_version/structurizr-cli.zip" -P ext
13 | unzip -o -d"$ext_dir" "$cli_zip"
14 | fi
15 |
16 | plantuml_version="1.2023.1"
17 | plantuml_jar="$ext_dir/plantuml-nodot.${plantuml_version}.jar"
18 | if [ ! -f "$plantuml_jar" ]; then
19 | echo
20 | echo "🔧 Downloading PlantUML..."
21 | wget "http://sourceforge.net/projects/plantuml/files/${plantuml_version}/plantuml-nodot.${plantuml_version}.jar/download" -O"$plantuml_jar"
22 | fi
23 |
24 | echo
25 | echo "🚧 Generating Structurizr workspaces..."
26 | "$root_dir/gradlew" run
27 |
28 | echo
29 | echo "🌿 Generating PlantUML..."
30 | (
31 | cd "$exports_dir"
32 | find "$root_dir" -name 'structurizr-*-local.json' \
33 | -exec "$ext_dir/structurizr.sh" export -workspace {} -f plantuml \;
34 | )
35 |
36 | echo
37 | echo "🖼 Generating images..."
38 | (
39 | cd "$exports_dir"
40 | java -Djava.awt.headless=true -jar "$plantuml_jar" -SmaxMessageSize=100 -tpng structurizr-*.puml
41 | )
42 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "hmpps-architecture-as-code"
2 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/App.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.Workspace
4 | import com.structurizr.api.StructurizrClient
5 | import com.structurizr.graphviz.GraphvizAutomaticLayout
6 | import com.structurizr.graphviz.RankDirection
7 | import com.structurizr.util.WorkspaceUtils
8 | import uk.gov.justice.hmpps.architecture.export.backstage.BackstageExporter
9 | import java.io.File
10 |
11 | object App {
12 | val EXPORT_LOCATION = File("./exports")
13 | val ACTIONS: Map Unit> = mapOf(
14 | "--push" to ::pushToRemote,
15 | "--backstage" to ::exportToBackstage
16 | )
17 |
18 | @JvmStatic
19 | fun main(args: Array) {
20 | val workspace = defineWorkspace()
21 |
22 | val graphviz = GraphvizAutomaticLayout(EXPORT_LOCATION)
23 | graphviz.setRankDirection(RankDirection.TopBottom)
24 | graphviz.setRankSeparation(400.0)
25 | graphviz.setNodeSeparation(300.0)
26 | graphviz.apply(workspace)
27 |
28 | if (args.isEmpty()) {
29 | writeToFile(workspace)
30 | return
31 | }
32 |
33 | args.forEach { arg -> ACTIONS.get(arg)?.invoke(workspace) }
34 | }
35 |
36 | private fun pushToRemote(workspace: Workspace) {
37 | val client = StructurizrClient(System.getenv("STRUCTURIZR_API_KEY"), System.getenv("STRUCTURIZR_API_SECRET"))
38 | val workspaceId = System.getenv("STRUCTURIZR_WORKSPACE_ID")?.toLongOrNull() ?: workspace.id
39 | client.workspaceArchiveLocation = EXPORT_LOCATION
40 | client.setMergeFromRemote(false)
41 |
42 | workspace.version = System.getenv("BUILD_VERSION")
43 | client.putWorkspace(workspaceId, workspace)
44 | }
45 |
46 | private fun exportToBackstage(workspace: Workspace) {
47 | val backstageExport = BackstageExporter().export(workspace)
48 | File(EXPORT_LOCATION, "backstage-${workspace.id}.yaml").writeText(backstageExport)
49 | }
50 |
51 | private fun writeToFile(workspace: Workspace) {
52 | val targetFile = File(EXPORT_LOCATION, "structurizr-${workspace.id}-local.json")
53 | WorkspaceUtils.saveWorkspaceToJson(workspace, targetFile)
54 | println("Wrote workspace to '$targetFile'")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/DefinesDecisions.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.Workspace
4 |
5 | interface DefinesDecisions {
6 | fun defineDecisions(workspace: Workspace)
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/HMPPSSoftwareSystem.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.view.ViewSet
5 |
6 | interface HMPPSSoftwareSystem {
7 | fun defineModelEntities(model: Model)
8 | fun defineRelationships()
9 | fun defineViews(views: ViewSet)
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/APIDocs.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 |
5 | @Deprecated("Do not annotate Containers. Instead, please add an API docs badge to the GitHub repo's README")
6 | class APIDocs(private val url: String) {
7 | fun addTo(element: Element) {
8 | element.addProperty("api-docs-url", url)
9 | }
10 |
11 | companion object {
12 | fun getFrom(element: Element): String? {
13 | return element.properties["api-docs-url"]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/Capability.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 |
5 | enum class Capability : addTo {
6 | IDENTITY;
7 |
8 | override fun addTo(element: Element) {
9 | element.addProperty("capability", this.toString())
10 | }
11 |
12 | fun isOn(element: Element): Boolean {
13 | return element.properties.get("capability") == this.toString()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/Notifier.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Person
5 | import uk.gov.justice.hmpps.architecture.Notify
6 |
7 | class Notifier {
8 | companion object : delivers {
9 | override fun delivers(from: Container, to: List, String, String>>) {
10 | from.uses(Notify.system, "delivers notifications via")
11 | to.map {
12 | val(people, label, style) = it
13 | people.map { from.delivers(it, label, style) }
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/OutsideHMPPS.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 | import com.structurizr.model.Location
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 |
8 | class OutsideHMPPS {
9 | companion object : addTo {
10 | override fun addTo(element: Element) {
11 | when (element) {
12 | is SoftwareSystem -> element.setLocation(Location.External)
13 | is Person -> element.setLocation(Location.External)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/ProblemArea.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 |
5 | enum class ProblemArea : addTo {
6 | GETTING_THE_RIGHT_REHABILITATION;
7 |
8 | override fun addTo(element: Element) {
9 | element.addProperty("problem-area", this.toString())
10 | }
11 |
12 | fun isOn(element: Element): Boolean {
13 | return element.properties.get("problem-area") == this.toString()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/Tags.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 |
5 | enum class Tags : addTo {
6 | DATABASE,
7 | TOPIC,
8 | QUEUE,
9 | WEB_BROWSER,
10 | REUSABLE_COMPONENT,
11 | PROVIDER {
12 | override fun addTo(element: Element) {
13 | super.addTo(element)
14 | OutsideHMPPS.addTo(element)
15 | }
16 | },
17 | PLANNED,
18 | DEPRECATED,
19 | SOFTWARE_AS_A_SERVICE,
20 | DATA_API,
21 | DOMAIN_API,
22 | AREA_PROBATION,
23 | AREA_PRISONS;
24 |
25 | // Usage example: Tags.DATABASE.addTo(any_model_element)
26 | override fun addTo(element: Element) {
27 | element.addTags(this.toString())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/addTo.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Element
4 |
5 | interface addTo {
6 | fun addTo(element: Element)
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/annotations/delivers.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.annotations
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Person
5 |
6 | interface delivers {
7 | fun delivers(
8 | from: Container,
9 | to: List, String, String>>
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/defineDocumentation.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.Workspace
4 | import com.structurizr.documentation.Format
5 | import com.structurizr.documentation.Section
6 | import com.structurizr.model.Container
7 | import uk.gov.justice.hmpps.architecture.annotations.APIDocs
8 | import uk.gov.justice.hmpps.architecture.documentation.BranchMetric
9 | import uk.gov.justice.hmpps.architecture.documentation.Version
10 | import uk.gov.justice.hmpps.architecture.documentation.parseBranchMetrics
11 | import uk.gov.justice.hmpps.architecture.documentation.parseVersions
12 | import java.io.File
13 | import java.io.IOException
14 | import java.io.PrintWriter
15 |
16 | fun defineDocumentation(workspace: Workspace) {
17 | val docsRoot = File(App.EXPORT_LOCATION, "docs/")
18 | docsRoot.mkdirs()
19 |
20 | val containersWithGitRepos = containersWithGit(workspace.model)
21 |
22 | val docs = File(docsRoot, "01-apidocs.md")
23 | docs.printWriter().let {
24 | writeAPIs(it, containersWithGitRepos)
25 | it.close()
26 | }
27 |
28 | val dependencies = File(docsRoot, "02-dependencies.md")
29 | dependencies.printWriter().let {
30 | writeDependencies(it, containersWithGitRepos)
31 | it.close()
32 | }
33 |
34 | val branches = File(docsRoot, "03-branches.md")
35 | branches.printWriter().let {
36 | writeBranchMetrics(it, containersWithGitRepos)
37 | it.close()
38 | }
39 |
40 | val template = workspace.documentation
41 | template.addSection(Section(Format.Markdown, docs.readText()))
42 | template.addSection(Section(Format.Markdown, dependencies.readText()))
43 | template.addSection(Section(Format.Markdown, branches.readText()))
44 | }
45 |
46 | private fun writeAPIs(w: PrintWriter, containersWithGit: List) {
47 | w.println("## Published APIs")
48 | w.println("")
49 |
50 | w.println("| Software System | API name | Purpose | Links |")
51 | w.println("| --- | --- | --- | --- |")
52 |
53 | containersWithAPIDocs(containersWithGit)
54 | .also { println("[defineDocumentation] Found ${it.size} APIs with documentation URLs") }
55 | .forEach { writeAPI(w, it) }
56 | }
57 |
58 | private fun writeDependencies(w: PrintWriter, containersWithGit: List) {
59 | w.println("## Dependencies")
60 | w.println("")
61 |
62 | w.println("- Latest [CircleCI orb](https://circleci.com/developer/orbs/orb/ministryofjustice/hmpps)")
63 | w.println("- Latest [gradle-spring-boot](https://plugins.gradle.org/plugin/uk.gov.justice.hmpps.gradle-spring-boot)")
64 | w.println("- Latest [helm charts](https://github.com/ministryofjustice/hmpps-helm-charts/releases)")
65 | w.println("")
66 |
67 | w.println("| Software System | Application | CircleCI orb versions | gradle-spring-boot version | helm charts")
68 | w.println("| --- | --- | --- | --- | --- |")
69 |
70 | parseVersions(containersWithGit)
71 | .forEach { writeDependency(w, it) }
72 | }
73 |
74 | fun writeDependency(w: PrintWriter, v: Version) {
75 | w.print("| ")
76 | w.print(v.softwareSystem)
77 |
78 | w.print("| ")
79 | w.print("[${v.application}](${v.applicationUrl})")
80 |
81 | w.print("| ")
82 | w.print(v.circleciOrbVersions.joinToString(", "))
83 |
84 | w.print("| ")
85 | w.print(v.gradleBootPluginVersion)
86 |
87 | w.print("| ")
88 | w.print(v.chartVersions.joinToString(", "))
89 |
90 | w.println("|")
91 | }
92 |
93 | private fun writeBranchMetrics(w: PrintWriter, containersWithGit: List) {
94 | w.println("## Branches")
95 | w.println("")
96 |
97 | w.println("| Software System | Application | Unmerged branches | Oldest unmerged branch | Oldest branch date")
98 | w.println("| --- | --- | --- | --- | --- |")
99 |
100 | parseBranchMetrics(containersWithGit)
101 | .forEach { writeBranchMetric(w, it) }
102 | }
103 |
104 | fun writeBranchMetric(w: PrintWriter, m: BranchMetric) {
105 | w.print("| ")
106 | w.print(m.softwareSystem)
107 |
108 | w.print("| ")
109 | w.print("[${m.application}](${m.applicationUrl})")
110 |
111 | w.print("| ")
112 | w.print(m.unmergedBranches)
113 |
114 | w.print("| ")
115 | w.print(m.oldestUnmergedBranch?.name ?: "")
116 |
117 | w.print("| ")
118 | w.print(m.oldestUnmergedBranch?.latestCommitTime?.toLocalDate() ?: "")
119 |
120 | w.println("|")
121 | }
122 |
123 | @Suppress("DEPRECATION")
124 | private fun writeAPI(w: PrintWriter, apiContainer: Container) {
125 | w.print("| ")
126 | w.print(apiContainer.softwareSystem.name)
127 |
128 | w.print("| ")
129 | w.print(apiContainer.name)
130 |
131 | w.print("| ")
132 | w.print(apiContainer.description)
133 |
134 | w.print("| ")
135 | val githubLink = apiContainer.url
136 | val apidocsLink = APIDocs.getFrom(apiContainer)
137 | w.print(" [GitHub](%s)".format(githubLink))
138 | w.print(" [APIdocs](%s)".format(apidocsLink))
139 |
140 | w.println("|")
141 | }
142 |
143 | @Suppress("DEPRECATION")
144 | private fun containersWithAPIDocs(containersWithGit: List): List {
145 | return containersWithGit
146 | .map { pullApiDocs(it) }
147 | .filter { APIDocs.getFrom(it) != null }
148 | }
149 |
150 | @Suppress("DEPRECATION")
151 | private fun pullApiDocs(container: Container): Container {
152 | val oldUrl = APIDocs.getFrom(container)
153 | val url = readAPIDocsURLFromRepoReadmeBadge(container)
154 |
155 | if (url != null) {
156 | if (oldUrl != null) {
157 | println("[defineDocumentation] overriding API docs URL\n - from ${oldUrl}\n - to $url")
158 | }
159 | APIDocs(url).addTo(container)
160 | }
161 | return container
162 | }
163 |
164 | val BADGE_URL_PATTERN = Regex("""\[!\[API docs]\(.*\)]\((.*)\)""")
165 | private fun readAPIDocsURLFromRepoReadmeBadge(app: Container): String? {
166 | var readmeContents = ""
167 | try {
168 | println()
169 | val repoDir = cloneRepository(app)
170 | val readme = repoDir?.listFiles { _, name -> name.equals("README.md", true) }?.first()
171 | readmeContents = readme?.readText().orEmpty()
172 | } catch (e: IOException) {
173 | println("[defineDocumentation] ignoring: $e")
174 | } catch (e: java.util.NoSuchElementException) {
175 | println("[defineDocumentation] ignoring: $e")
176 | }
177 |
178 | val m = BADGE_URL_PATTERN.find(readmeContents)
179 | return m?.groups?.get(1)?.value
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/defineGlobalViews.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.AutomaticLayout
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
8 | import uk.gov.justice.hmpps.architecture.model.AWS
9 | import uk.gov.justice.hmpps.architecture.model.AzureADTenantJusticeUK
10 | import uk.gov.justice.hmpps.architecture.model.Delius
11 | import uk.gov.justice.hmpps.architecture.model.HMPPSAuth
12 | import uk.gov.justice.hmpps.architecture.model.MoJSignOn
13 | import uk.gov.justice.hmpps.architecture.model.NDH
14 | import uk.gov.justice.hmpps.architecture.model.NOMIS
15 |
16 | fun defineGlobalViews(model: Model, views: ViewSet) {
17 | views.createSystemLandscapeView("system-overview", "All systems").apply {
18 | addAllSoftwareSystems()
19 |
20 | val noisySignOnSystems = listOf(HMPPSAuth.system, MoJSignOn.system, AzureADTenantJusticeUK.system)
21 | val noisyHubSystems = listOf(NDH.system)
22 | noisySignOnSystems.forEach(::remove)
23 | noisyHubSystems.forEach(::remove)
24 | }
25 |
26 | // lifted from probation views
27 | views.createSystemLandscapeView(
28 | "getting-the-right-rehabilitation",
29 | "Landscape view of the 'getting the right rehabilitation' problem area"
30 | ).apply {
31 | val interventionSystems = model.softwareSystems.filter { ProblemArea.GETTING_THE_RIGHT_REHABILITATION.isOn(it) }
32 | interventionSystems.forEach { addNearestNeighbours(it) }
33 |
34 | val otherSystems = getElements().map { it.element }
35 | .filterIsInstance()
36 | .filterNot { ProblemArea.GETTING_THE_RIGHT_REHABILITATION.isOn(it) }
37 | otherSystems.forEach { remove(it) }
38 |
39 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 400, 400)
40 | }
41 |
42 | // lifted from prison views
43 | val pathfinder = model.getSoftwareSystemWithName("Pathfinder")!!
44 | views.createContainerView(pathfinder, "pathfinderContainer", "The container diagram for the Pathfinder System.").apply {
45 | model.people.filter { it.hasEfferentRelationshipWith(pathfinder) }.map { add(it) }
46 | pathfinder.containers.map { add(it) }
47 | add(NOMIS.db)
48 | add(NOMIS.prisonApi)
49 | add(NOMIS.system.getContainerWithName("ElasticSearch store"))
50 | add(NOMIS.offenderSearch)
51 | add(Delius.database)
52 | add(Delius.communityApi)
53 | add(Delius.offenderElasticsearchStore)
54 | add(Delius.offenderSearch)
55 | add(HMPPSAuth.system)
56 | enableAutomaticLayout()
57 | externalSoftwareSystemBoundariesVisible = true
58 | }
59 |
60 | views.createSystemContextView(
61 | pathfinder,
62 | "PathfinderSystemContext",
63 | "The system context diagram for the Pathfinder System."
64 | ).apply {
65 | addDefaultElements()
66 | add(NOMIS.system)
67 | add(Delius.system)
68 | add(HMPPSAuth.system)
69 | enableAutomaticLayout()
70 | }
71 |
72 | views.createDeploymentView(pathfinder, "PathfinderProductionDeployment", "The Production deployment scenario for the Pathfinder service").apply {
73 | add(AWS.london)
74 | enableAutomaticLayout()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/defineModelWithDeprecatedSyntax.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.model.Model
4 | import uk.gov.justice.hmpps.architecture.model.Pathfinder
5 | import uk.gov.justice.hmpps.architecture.model.Reporting
6 |
7 | fun defineModelWithDeprecatedSyntax(model: Model) {
8 |
9 | Pathfinder(model)
10 |
11 | // lifted from probation model
12 | val hmip = model.addPerson("HM Inspectorate of Probation", "Reports to the government on the effectiveness of work with people who offended to reduce reoffending and protect the public")
13 |
14 | hmip.uses(Reporting.ndmis, "uses data from")
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/defineStyles.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.view.Border
4 | import com.structurizr.view.Shape
5 | import com.structurizr.view.Styles
6 | import uk.gov.justice.hmpps.architecture.annotations.Tags
7 |
8 | val softwareColour = "#250469"
9 | val dbColour = "#1A6926"
10 | val personColour = "#7E68AB"
11 | val pipeColour = "#B56107"
12 | val textColour = "#FFFFFF"
13 | val providerColour = "#683D0F"
14 |
15 | fun defineStyles(styles: Styles) {
16 |
17 | styles.addElementStyle("Software System")
18 | .shape(Shape.RoundedBox)
19 | .background(softwareColour)
20 | .color(textColour)
21 |
22 | styles.addElementStyle("Container")
23 | .shape(Shape.RoundedBox)
24 | .background(softwareColour)
25 | .color(textColour)
26 |
27 | styles.addElementStyle("Person")
28 | .shape(Shape.Person)
29 | .background(personColour)
30 | .color(textColour)
31 |
32 | styles.addElementStyle(Tags.DATABASE.toString())
33 | .shape(Shape.Cylinder)
34 | .background(dbColour)
35 | .color(textColour)
36 |
37 | styles.addElementStyle(Tags.TOPIC.toString())
38 | .shape(Shape.Pipe)
39 | .background(pipeColour)
40 | .color(textColour)
41 |
42 | styles.addElementStyle(Tags.QUEUE.toString())
43 | .shape(Shape.Pipe)
44 | .background(pipeColour)
45 | .color(textColour)
46 |
47 | styles.addElementStyle(Tags.WEB_BROWSER.toString()).shape(Shape.WebBrowser)
48 | styles.addElementStyle(Tags.REUSABLE_COMPONENT.toString()).shape(Shape.Hexagon)
49 | styles.addElementStyle(Tags.PLANNED.toString()).border(Border.Dotted).opacity(50)
50 |
51 | styles.addElementStyle(Tags.PROVIDER.toString()).background(providerColour)
52 | styles.addElementStyle(Tags.DEPRECATED.toString()).background("#999999")
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/documentation/codeTracker.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.documentation
2 |
3 | import com.structurizr.model.Container
4 | import org.eclipse.jgit.api.Git
5 | import uk.gov.justice.hmpps.architecture.cloneRepository
6 | import java.io.File
7 | import java.time.Instant
8 | import java.time.OffsetDateTime
9 | import java.time.ZoneId
10 | import java.util.concurrent.TimeUnit
11 |
12 | data class Branch(
13 | val name: String,
14 | val latestCommitTime: OffsetDateTime,
15 | )
16 |
17 | data class BranchMetric(
18 | val softwareSystem: String,
19 | val application: String,
20 | val applicationUrl: String,
21 |
22 | val unmergedBranches: Int,
23 | val oldestUnmergedBranch: Branch?,
24 | )
25 |
26 | fun parseBranchMetrics(containersWithGit: List): List {
27 | return containersWithGit
28 | .also { println("[parseBranchMetrics] Found ${it.size} applications with github repos") }
29 | .mapNotNull { parseBranchMetric(it) }
30 | }
31 |
32 | private fun parseBranchMetric(container: Container): BranchMetric? {
33 | val r = cloneRepository(container) ?: return null
34 |
35 | val branches = remoteBranches(r).map {
36 | val repo = Git.open(r).repository
37 | val ref = repo.resolve(it)
38 | val epochSeconds = repo.parseCommit(ref).commitTime.toLong()
39 | Branch(
40 | name = repo.shortenRemoteBranchName(it),
41 | latestCommitTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.of("Europe/London")),
42 | )
43 | }
44 |
45 | val oldestBranch = branches.minByOrNull { it.latestCommitTime }
46 |
47 | return BranchMetric(
48 | softwareSystem = container.softwareSystem.name,
49 | application = container.name,
50 | applicationUrl = container.url,
51 |
52 | unmergedBranches = branches.size,
53 | oldestUnmergedBranch = oldestBranch,
54 | )
55 | }
56 |
57 | private fun remoteBranches(repoDir: File, mainBranch: String = "main"): List {
58 | val p = ProcessBuilder(
59 | "git",
60 | "branch",
61 | "--remote",
62 | "--list",
63 | "--no-merged=origin/$mainBranch",
64 | "--format=%(refname)",
65 | "--sort=committerdate"
66 | )
67 | .directory(repoDir)
68 | .start()
69 | .also { it.waitFor(1, TimeUnit.SECONDS) }
70 |
71 | if (p.exitValue() != 0) {
72 | val message = p.errorStream.bufferedReader().readText()
73 | return if (message.contains("malformed object name origin/main")) {
74 | remoteBranches(repoDir, "master")
75 | } else {
76 | System.err.println("[parseBranchMetric] cannot retrieve branches: $message")
77 | listOf()
78 | }
79 | }
80 |
81 | return p.inputStream.bufferedReader().readLines()
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/documentation/versionTracker.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.documentation
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper
4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
5 | import com.structurizr.model.Container
6 | import uk.gov.justice.hmpps.architecture.cloneRepository
7 | import java.io.File
8 |
9 | data class Version(
10 | val softwareSystem: String,
11 | val application: String,
12 | val applicationUrl: String,
13 | val circleciOrbVersions: List,
14 | val gradleBootPluginVersion: String,
15 | val chartVersions: List,
16 | )
17 |
18 | fun parseVersions(containersWithGit: List): List {
19 | return containersWithGit
20 | .also { println("[parseVersions] Found ${it.size} applications with github repos") }
21 | .mapNotNull { parseVersion(it) }
22 | }
23 |
24 | private fun parseVersion(container: Container): Version? {
25 | val r = cloneRepository(container) ?: return null
26 | return Version(
27 | softwareSystem = container.softwareSystem.name,
28 | application = container.name,
29 | applicationUrl = container.url,
30 | circleciOrbVersions = readCircleOrbVersion(r),
31 | gradleBootPluginVersion = readGradlePluginVersion(r),
32 | chartVersions = readHelmChartDependencies(r)
33 | )
34 | }
35 |
36 | private fun readCircleOrbVersion(repo: File): List {
37 | val circleConfig = repo.resolve(".circleci").resolve("config.yml").takeIf { it.exists() }?.readText().orEmpty()
38 |
39 | val circleOrb = Regex("ministryofjustice/(hmpps@[\\d.]*)").find(circleConfig)?.groups?.get(1)?.value
40 | val dpsOrb = Regex("ministryofjustice/(dps@[\\d.]*)").find(circleConfig)?.groups?.get(1)?.value
41 | return listOfNotNull(circleOrb, dpsOrb)
42 | }
43 |
44 | const val GRADLE_PATTERN = """id\("uk.gov.justice.hmpps.gradle-spring-boot"\) version "([^"]*)""""
45 | private fun readGradlePluginVersion(repo: File): String {
46 | val buildFileKt = repo.resolve("build.gradle.kts").takeIf { it.exists() }?.readText().orEmpty()
47 | val buildFile = repo.resolve("build.gradle").takeIf { it.exists() }?.readText().orEmpty()
48 |
49 | return listOfNotNull(
50 | Regex(GRADLE_PATTERN).find(buildFileKt)?.groups?.get(1)?.value,
51 | Regex(GRADLE_PATTERN).find(buildFile)?.groups?.get(1)?.value,
52 | ).firstOrNull() ?: ""
53 | }
54 |
55 | private fun readHelmChartDependencies(repo: File): List {
56 | val chartFile = repo.resolve("helm_deploy")
57 | .walkTopDown().find { it.name == "Chart.yaml" }
58 | ?: return listOf("no helm chart")
59 |
60 | val dependencies = ObjectMapper(YAMLFactory()).readTree(chartFile).get("dependencies")
61 | ?: return listOf("standalone chart")
62 |
63 | return dependencies.map { "${it.get("name").textValue()}@${it.get("version").textValue()}" }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/BackstageExporter.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper
4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
5 | import com.fasterxml.jackson.module.kotlin.KotlinModule
6 | import com.structurizr.Workspace
7 | import com.structurizr.model.Container
8 | import com.structurizr.model.SoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 | import uk.gov.justice.hmpps.architecture.export.backstage.apispec.OpenApiSpecFinder
11 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageAPI
12 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageAPISpec
13 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageAPISpecDefinition
14 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageAPISpecType.OPEN_API
15 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponent
16 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpec
17 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType
18 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType.DATABASE
19 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType.FRONTEND
20 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType.QUEUE
21 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType.SERVICE
22 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageComponentSpecType.TOPIC
23 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageLifecycle
24 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageLifecycle.DEPRECATED
25 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageLifecycle.PRODUCTION
26 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageMetadata
27 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageMetadataLink
28 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageMetadataLinkIcon.GITHUB
29 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageSystem
30 | import java.util.Locale
31 |
32 | class BackstageExporter(private val openApiSpecFinder: OpenApiSpecFinder = OpenApiSpecFinder()) {
33 |
34 | private val objectMapper: ObjectMapper = ObjectMapper(YAMLFactory())
35 |
36 | init {
37 | objectMapper.registerModule(KotlinModule.Builder().build())
38 | }
39 |
40 | fun export(workspace: Workspace): String {
41 | val allSystems = workspace.model.softwareSystems.map { it.convert() }
42 | val allComponents = allSystems.flatMap { it.components }
43 | val allApis = allSystems.flatMap { it.components.mapNotNull { component -> component.api } }
44 | val allModels = allSystems
45 | .plus(allComponents)
46 | .plus(allApis)
47 |
48 | return allModels
49 | .sortedBy { it.metadata.name }
50 | .map { objectMapper.writeValueAsString(it) }
51 | .joinToString("")
52 | }
53 |
54 | private fun SoftwareSystem.convert(): BackstageSystem {
55 | return BackstageSystem(
56 | metadata = BackstageMetadata(
57 | name = backstageId(),
58 | title = name,
59 | description = description,
60 | ),
61 | components = containers.map { it.convert() }
62 | )
63 | }
64 |
65 | private fun Container.githubLink(): BackstageMetadataLink? {
66 | return url?.let {
67 | BackstageMetadataLink(
68 | url = it,
69 | title = "GitHub Repo",
70 | icon = GITHUB,
71 | )
72 | }
73 | }
74 |
75 | private fun Container.apiDocsLink(): BackstageMetadataLink? {
76 | return this.properties[API_DOCS_URL]?.let {
77 | BackstageMetadataLink(
78 | url = it,
79 | title = "API Docs",
80 | )
81 | }
82 | }
83 |
84 | private fun Container.lifecycle(): BackstageLifecycle {
85 | return if (hasTag(Tags.DEPRECATED.name)) DEPRECATED else PRODUCTION
86 | }
87 |
88 | private fun Container.type(): BackstageComponentSpecType {
89 | return when {
90 | hasTag(Tags.DATABASE.name) -> DATABASE
91 | hasTag(Tags.QUEUE.name) -> QUEUE
92 | hasTag(Tags.TOPIC.name) -> TOPIC
93 | hasTag(Tags.WEB_BROWSER.name) -> FRONTEND
94 | else -> SERVICE
95 | }
96 | }
97 |
98 | private fun Container.api(): BackstageAPI? {
99 | return properties[API_DOCS_URL]
100 | ?.let { openApiSpecFinder.deriveApiSpecFor(it) }
101 | ?.let {
102 | BackstageAPI(
103 | metadata = BackstageMetadata(
104 | name = backstageId(),
105 | description = "API provided by ${backstageId()}",
106 | ),
107 | spec = BackstageAPISpec(
108 | type = OPEN_API,
109 | lifecycle = lifecycle(),
110 | system = softwareSystem.backstageId(),
111 | definition = BackstageAPISpecDefinition(text = it),
112 | )
113 | )
114 | }
115 | }
116 |
117 | private fun Container.convert(): BackstageComponent {
118 | val api = api()
119 | val relationships = relationships
120 | .map { it.destination }
121 | .filterIsInstance()
122 |
123 | return BackstageComponent(
124 | metadata = BackstageMetadata(
125 | name = backstageId(),
126 | title = name,
127 | description = description,
128 | links = listOfNotNull(githubLink(), apiDocsLink()),
129 | annotations = url?.let { mapOf("backstage.io/source-location" to "url:$url") }
130 | ),
131 | spec = BackstageComponentSpec(
132 | type = type(),
133 | lifecycle = lifecycle(),
134 | system = softwareSystem.backstageId(),
135 | providesApis = listOfNotNull(api?.metadata?.name),
136 | consumesApis = relationships.map { it.backstageId() },
137 | dependsOn = relationships.map { "Component:${it.backstageId()}" },
138 | ),
139 | api = api,
140 | )
141 | }
142 |
143 | private fun SoftwareSystem.backstageId(): String {
144 | return backstageId(name)
145 | }
146 |
147 | private fun Container.backstageId(): String {
148 | val idWithContext = backstageId("${softwareSystem.name}-$name")
149 | val genericContainerName = "^(database|backend|frontend)$".toRegex()
150 |
151 | if (genericContainerName.matches(name.lowercase(Locale.getDefault()))) return idWithContext
152 |
153 | if ("api".equals(name, ignoreCase = true)) {
154 | return if (softwareSystem.name.endsWith("api", ignoreCase = true)) softwareSystem.name else idWithContext
155 | }
156 |
157 | return backstageId(name)
158 | }
159 |
160 | private fun backstageId(name: String): String {
161 | return name.lowercase().replace(Regex("""[^a-z0-9]+"""), "-")
162 | }
163 |
164 | private companion object {
165 | const val API_DOCS_URL = "api-docs-url"
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/apispec/OpenApiSpecFinder.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.apispec
2 |
3 | class OpenApiSpecFinder {
4 |
5 | private val specRegex = "/swagger-ui.*".toRegex()
6 | private val unhostedUiRegex = "https://editor.swagger.io/\\?url=(.*)".toRegex()
7 |
8 | fun deriveApiSpecFor(swaggerUiUrl: String): String? {
9 |
10 | if (unhostedUiRegex.matches(swaggerUiUrl)) {
11 | return unhostedUiRegex.matchEntire(swaggerUiUrl)?.groupValues?.get(1)
12 | }
13 |
14 | return specRegex.replace(swaggerUiUrl, "/v3/api-docs")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageAPI.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty
4 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageModelType.API
5 |
6 | enum class BackstageAPISpecType {
7 | @JsonProperty("openapi") OPEN_API,
8 | }
9 |
10 | data class BackstageAPISpecDefinition(
11 | @JsonProperty("\$text") val text: String
12 | )
13 |
14 | data class BackstageAPISpec(
15 | val type: BackstageAPISpecType,
16 | val lifecycle: BackstageLifecycle,
17 | val system: String,
18 | val owner: String = "hmpps-undefined",
19 | val definition: BackstageAPISpecDefinition,
20 | )
21 |
22 | data class BackstageAPI(
23 | override val metadata: BackstageMetadata,
24 | val spec: BackstageAPISpec,
25 | ) : BackstageModel(kind = API, metadata = metadata)
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageComponent.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore
4 | import com.fasterxml.jackson.annotation.JsonInclude
5 | import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
6 | import com.fasterxml.jackson.annotation.JsonProperty
7 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageModelType.COMPONENT
8 |
9 | enum class BackstageComponentSpecType {
10 | @JsonProperty("service") SERVICE,
11 | @JsonProperty("database") DATABASE,
12 | @JsonProperty("queue") QUEUE,
13 | @JsonProperty("topic") TOPIC,
14 | @JsonProperty("frontend") FRONTEND,
15 | }
16 |
17 | @JsonInclude(NON_EMPTY)
18 | data class BackstageComponentSpec(
19 | val type: BackstageComponentSpecType,
20 | val lifecycle: BackstageLifecycle,
21 | val system: String,
22 | val owner: String = "hmpps-undefined",
23 | val dependsOn: List = emptyList(),
24 | val providesApis: List = emptyList(),
25 | val consumesApis: List = emptyList(),
26 | )
27 |
28 | data class BackstageComponent(
29 | override val metadata: BackstageMetadata,
30 | val spec: BackstageComponentSpec,
31 |
32 | @JsonIgnore
33 | val api: BackstageAPI? = null,
34 | ) : BackstageModel(kind = COMPONENT, metadata = metadata)
35 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageLifecycle.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty
4 |
5 | enum class BackstageLifecycle {
6 | @JsonProperty("deprecated") DEPRECATED,
7 | @JsonProperty("production") PRODUCTION,
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageMetadata.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude
4 | import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
5 | import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
6 | import com.fasterxml.jackson.annotation.JsonProperty
7 |
8 | enum class BackstageMetadataLinkIcon {
9 | @JsonProperty("github") GITHUB
10 | }
11 |
12 | @JsonInclude(NON_NULL)
13 | data class BackstageMetadataLink(
14 | val url: String,
15 | val title: String,
16 | val icon: BackstageMetadataLinkIcon? = null
17 | )
18 |
19 | @JsonInclude(NON_EMPTY)
20 | data class BackstageMetadata(
21 | val name: String,
22 | val title: String? = null,
23 | val description: String? = null,
24 | val links: List? = null,
25 | val annotations: Map? = null,
26 | )
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageModel.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty
4 | import com.fasterxml.jackson.annotation.JsonPropertyOrder
5 |
6 | enum class BackstageModelType {
7 | @JsonProperty("System") SYSTEM,
8 | @JsonProperty("Component") COMPONENT,
9 | @JsonProperty("API") API,
10 | }
11 |
12 | @JsonPropertyOrder("apiVersion", "kind")
13 | abstract class BackstageModel(
14 | val apiVersion: String = "backstage.io/v1alpha1",
15 | val kind: BackstageModelType,
16 | open val metadata: BackstageMetadata,
17 | )
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/model/BackstageSystem.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage.model
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore
4 | import uk.gov.justice.hmpps.architecture.export.backstage.model.BackstageModelType.SYSTEM
5 |
6 | data class BackstageSystemSpec(
7 | val owner: String = "hmpps-undefined",
8 | )
9 |
10 | data class BackstageSystem(
11 | override val metadata: BackstageMetadata,
12 | val spec: BackstageSystemSpec = BackstageSystemSpec(),
13 |
14 | @JsonIgnore
15 | val components: List = emptyList()
16 | ) : BackstageModel(kind = SYSTEM, metadata = metadata)
17 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/git.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Element
5 | import com.structurizr.model.Model
6 | import org.eclipse.jgit.api.Git
7 | import org.eclipse.jgit.api.MergeCommand
8 | import org.eclipse.jgit.api.errors.GitAPIException
9 | import java.io.File
10 |
11 | fun containersWithGit(model: Model): List {
12 | return model.softwareSystems
13 | .flatMap { it.containers }
14 | .sortedBy { it.softwareSystem.name.lowercase() + it.name.lowercase() }
15 | .filter { it.url.orEmpty().startsWith("https://github.com/ministryofjustice") }
16 | }
17 |
18 | fun cloneRepository(e: Element): File? {
19 | return cloneRepository(e.url)
20 | }
21 |
22 | fun cloneRepository(repoUrl: String): File? {
23 | val cloneDir = App.EXPORT_LOCATION.resolve("clones").resolve(File(repoUrl).name)
24 |
25 | try {
26 | if (cloneDir.resolve(".git").exists()) {
27 | println("[git] $repoUrl already cloned -- doing nothing (force new clone with rm -rf exports/clones/)")
28 | } else {
29 | println("[git] Cloning $repoUrl")
30 | clone(repoUrl, cloneDir)
31 | }
32 | } catch (e: GitAPIException) {
33 | println("[git] ignoring: $e")
34 | return null
35 | }
36 |
37 | return cloneDir
38 | }
39 |
40 | private fun clone(repoUrl: String, cloneDir: File) {
41 | Git.cloneRepository()
42 | .setURI(repoUrl)
43 | .setDirectory(cloneDir)
44 | .call()
45 | }
46 |
47 | private fun update(cloneDir: File) {
48 | Git.open(cloneDir)
49 | .pull()
50 | .setFastForward(MergeCommand.FastForwardMode.FF_ONLY)
51 | .call()
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/AWS.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.DeploymentNode
4 | import com.structurizr.model.Model
5 |
6 | class AWS private constructor() {
7 | companion object {
8 | lateinit var london: DeploymentNode
9 |
10 | fun defineDeploymentNodes(model: Model) {
11 | val root = model.addDeploymentNode("Amazon Web Services", "AWS platform", "AWS")
12 | london = root.addDeploymentNode("London region (eu-west-2)")
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/AdjudicationsApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class AdjudicationsApi private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Adjudications API",
18 | "Allows reporting and viewing of Adjudications"
19 | ).apply {
20 | Tags.DOMAIN_API.addTo(this)
21 | Tags.AREA_PRISONS.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/hmpps-manage-adjudications-api"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for adjudication information", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | api.uses(NOMIS.prisonApi, "retrieves and creates adjudication information in")
38 | }
39 |
40 | override fun defineViews(views: ViewSet) {
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/AnalyticalPlatform.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class AnalyticalPlatform private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var landingBucket: Container
12 |
13 | override fun defineModelEntities(model: Model) {
14 | val platform = model.addSoftwareSystem("Analytical Platform")
15 | landingBucket = platform.addContainer(
16 | "Analytical Platform landing bucket",
17 | "Storage area where data ingestion for analytics and data science starts",
18 | "S3"
19 | ).apply {
20 | Tags.DATABASE.addTo(this)
21 | }
22 | }
23 |
24 | override fun defineRelationships() {
25 | }
26 |
27 | override fun defineViews(views: ViewSet) {
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/AssessRisksAndNeeds.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class AssessRisksAndNeeds private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var riskNeedsService: Container
15 | lateinit var collector: Container
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem(
19 | "Assess Risks And Needs API",
20 | "API Service for exposing risk and needs information to other digital services"
21 | )
22 |
23 | val assessRiskNeedsDb = system.addContainer(
24 | "Assess Risks and Needs Database",
25 | "Holds offender risks and needs data, Authoritative source for supplementary risk data",
26 | "PostgreSQL"
27 | ).apply {
28 | Tags.DATABASE.addTo(this)
29 | CloudPlatform.rds.add(this)
30 | }
31 |
32 | riskNeedsService = system.addContainer(
33 | "Assess Risks and Needs API",
34 | "Source of risk and needs data for offenders",
35 | "Kotlin + Spring Boot"
36 | ).apply {
37 | Tags.DOMAIN_API.addTo(this)
38 | Tags.AREA_PROBATION.addTo(this)
39 | uses(assessRiskNeedsDb, "connects to", "JDBC")
40 | url = "https://github.com/ministryofjustice/hmpps-assess-risks-and-needs"
41 | CloudPlatform.kubernetes.add(this)
42 | }
43 | }
44 |
45 | override fun defineRelationships() {
46 | riskNeedsService.uses(HMPPSAuth.system, "authenticates via")
47 | riskNeedsService.uses(Delius.communityApi, "performs check when requesting data on LAOs")
48 | riskNeedsService.uses(OASys.assessmentsApi, "get offender past assessment details from")
49 | riskNeedsService.uses(OASys.assessmentsApi, "get offender risk and needs data from")
50 | }
51 |
52 | override fun defineViews(views: ViewSet) {
53 | views.createSystemContextView(
54 | system,
55 | "assess-risks-and-needs-context", null
56 | ).apply {
57 | addDefaultElements()
58 | removeRelationshipsNotConnectedToElement(system)
59 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
60 | }
61 |
62 | views.createDeploymentView(system, "assess-risks-and-needs-deployment", "Deployment overview of the assess risks and needs services").apply {
63 | add(AWS.london)
64 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
65 | }
66 |
67 | views.createContainerView(system, "assess-risks-and-needs-container", null).apply {
68 | addDefaultElements()
69 | addAllContainersAndInfluencers()
70 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Azure.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.DeploymentNode
4 | import com.structurizr.model.Model
5 |
6 | class Azure private constructor() {
7 | companion object {
8 | lateinit var root: DeploymentNode
9 | lateinit var nomsdigitech: DeploymentNode
10 | lateinit var sequation: DeploymentNode
11 | lateinit var kubernetes: DeploymentNode
12 |
13 | fun defineDeploymentNodes(model: Model) {
14 | root = model.addDeploymentNode("Microsoft Azure", "Azure platform", "Azure")
15 | nomsdigitech = root.addDeploymentNode("NOMS Digital Studio")
16 | sequation = root.addDeploymentNode("Sequation")
17 | kubernetes = root.addDeploymentNode("Digital Studio AKS", "Digital Studio AKS Kubernetes Cluster", "Kubernetes")
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/AzureADTenantJusticeUK.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 |
9 | class AzureADTenantJusticeUK private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var directory: Container
12 | lateinit var system: SoftwareSystem
13 |
14 | override fun defineModelEntities(model: Model) {
15 | system = model.addSoftwareSystem(
16 | "Azure AD JusticeUK Tenant",
17 | "Azure AD tenant synced with uses from the on-prem Atos AD domain (DOM1)"
18 | )
19 |
20 | directory = system.addContainer(
21 | "JusticeUK Directory",
22 | "Directory service containing DOM1 identities",
23 | "Azure AD"
24 | ).apply {
25 | Azure.root.add(this)
26 | }
27 | }
28 |
29 | override fun defineRelationships() {
30 | }
31 |
32 | override fun defineViews(views: ViewSet) {
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/BookVideoLink.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.Notifier
11 |
12 | class BookVideoLink private constructor() {
13 |
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var model: Model
16 | lateinit var system: SoftwareSystem
17 | lateinit var bookVideoLinkService: Container
18 |
19 | lateinit var vccStaff: Person
20 | lateinit var omuStaff: Person
21 | lateinit var courtStaff: Person
22 |
23 | override fun defineModelEntities(model: Model) {
24 | this.model = model
25 |
26 | system = model.addSoftwareSystem(
27 | "Book Video Link",
28 | """
29 | The Book video link service allows court users to book video conferencing rooms inside a prison to allow Prisoners to attend court sessions.
30 | """.trimIndent()
31 | ).apply {
32 | val PRISON_PROBATION_PROPERTY_NAME = "business_unit"
33 | val PRISON_SERVICE = "prisons"
34 | addProperty(PRISON_PROBATION_PROPERTY_NAME, PRISON_SERVICE)
35 | }
36 |
37 | bookVideoLinkService = system.addContainer("Book video link service", "Allows courts to book, cancel and amend video calls with Prisoners", "Node").apply {
38 | setUrl("https://github.com/ministryofjustice/hmpps-book-video-link")
39 |
40 | CloudPlatform.kubernetes.add(this)
41 | }
42 |
43 | courtStaff = model.addPerson("Court bookings manager", "Court user who manages bookings").apply {
44 | uses(bookVideoLinkService, "Views, creates, requests and updates bookings ")
45 | }
46 |
47 | vccStaff = model.addPerson("VCC staff user", "Prison staff who manage the video conferencing suite")
48 |
49 | omuStaff = model.addPerson("OMU staff user", "Prison staff who works in the offender management unit")
50 | }
51 | override fun defineRelationships() {
52 | bookVideoLinkService.uses(NOMIS.prisonApi, "HTTPS Rest API")
53 | bookVideoLinkService.uses(HMPPSAuth.system, "HTTPS Rest API")
54 | bookVideoLinkService.uses(WhereaboutsApi.api, "HTTPS Rest API")
55 | bookVideoLinkService.uses(CourtRegister.api, "HTTPS Rest API")
56 | bookVideoLinkService.uses(TokenVerificationApi.api, "validates API tokens via", "HTTPS Rest API")
57 | bookVideoLinkService.uses(UserPreferenceApi.api, "Stores user's preferred courts in", "HTTPS Rest API")
58 |
59 | Notifier.delivers(
60 | bookVideoLinkService,
61 | listOf(
62 | Triple(
63 | listOf(courtStaff, vccStaff, omuStaff),
64 | "emails booking/amendment/cancellation confirmations",
65 | "email"
66 | ),
67 | )
68 | )
69 | }
70 |
71 | override fun defineViews(views: ViewSet) {
72 | views.createSystemContextView(
73 | system, "bookVideoLinkSystemContext", "The system context diagram for the Book Video Link service"
74 | ).apply {
75 | addDefaultElements()
76 |
77 | remove(vccStaff)
78 | remove(omuStaff)
79 |
80 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
81 | }
82 |
83 | views.createSystemContextView(
84 | system, "bookVideoLinkUserRelationships", "Relationships between users and the Book Video Link service"
85 | ).apply {
86 | add(system)
87 | add(vccStaff)
88 | add(omuStaff)
89 | add(courtStaff)
90 |
91 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
92 | }
93 |
94 | views.createContainerView(system, "bookVideoLinkContainer", "Book Video Link service container view").apply {
95 | addDefaultElements()
96 | remove(vccStaff)
97 | remove(omuStaff)
98 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
99 | }
100 |
101 | views.createDeploymentView(system, "bookVideoLinkContainerProductionDeployment", "The Production deployment scenario for the Book Video Link service").apply {
102 | add(AWS.london)
103 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CRCSystem.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class CRCSystem private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var integratedSystem: SoftwareSystem
13 | lateinit var standaloneSystem: SoftwareSystem
14 |
15 | override fun defineModelEntities(model: Model) {
16 | integratedSystem = model.addSoftwareSystem(
17 | "Integrated CRC software systems",
18 | "Various systems used to plan, schedule, manage interventions and bookings"
19 | )
20 |
21 | standaloneSystem = model.addSoftwareSystem(
22 | "Standalone CRC software systems",
23 | "Various systems used to plan, schedule, manage interventions and bookings"
24 | )
25 |
26 | listOf(integratedSystem, standaloneSystem).forEach {
27 | Tags.PROVIDER.addTo(it)
28 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(it)
29 | }
30 | }
31 |
32 | override fun defineRelationships() {
33 | integratedSystem.uses(Delius.system, "(some systems, not all) synchronise data with", "SPG")
34 | ProbationPractitioners.crc.uses(integratedSystem, "retrieves and stores information related to work done by the CRC in")
35 | ProbationPractitioners.crc.uses(standaloneSystem, "retrieves and stores information related to work done by the CRC in")
36 | }
37 |
38 | override fun defineViews(views: ViewSet) {
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CaseNotesToProbation.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class CaseNotesToProbation private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var system: SoftwareSystem
11 |
12 | override fun defineModelEntities(model: Model) {
13 | system = model.addSoftwareSystem(
14 | "Case Notes to Probation",
15 | "Polls for case notes and pushes them to probation systems"
16 | )
17 | }
18 |
19 | override fun defineRelationships() {
20 | system.uses(Delius.system, "pushes case notes to")
21 | }
22 |
23 | override fun defineViews(views: ViewSet) {
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CloudPlatform.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.DeploymentNode
4 | import com.structurizr.model.Model
5 |
6 | class CloudPlatform private constructor() {
7 | companion object {
8 | lateinit var rds: DeploymentNode
9 | lateinit var s3: DeploymentNode
10 | lateinit var sns: DeploymentNode
11 | lateinit var sqs: DeploymentNode
12 | lateinit var elasticache: DeploymentNode
13 | lateinit var elasticsearch: DeploymentNode
14 | lateinit var kubernetes: DeploymentNode
15 |
16 | fun defineDeploymentNodes(@Suppress("UNUSED_PARAMETER") model: Model) {
17 | var cloudPlatform = AWS.london.addDeploymentNode("Cloud Platform account", "AWS shared hosting platform", "AWS")
18 | rds = cloudPlatform.addDeploymentNode("RDS", "AWS Relational Database Service database-as-a-service", "AWS")
19 | s3 = cloudPlatform.addDeploymentNode("S3", "AWS Simple Storage Service", "AWS")
20 | sns = cloudPlatform.addDeploymentNode("SNS", "AWS Simple Notification Service", "AWS")
21 | sqs = cloudPlatform.addDeploymentNode("SQS", "AWS Simple Queue Service", "AWS")
22 | elasticache = cloudPlatform.addDeploymentNode("ElastiCache", "Managed in-memory data store and cache service", "AWS")
23 | elasticsearch = cloudPlatform.addDeploymentNode("ElasticSearch", "AWS ElasticSearch Service", "AWS")
24 | kubernetes = cloudPlatform.addDeploymentNode("Kubernetes", "The Cloud Platform Kubernetes cluster", "Kubernetes")
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ComplexityOfNeed.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class ComplexityOfNeed private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var microservice: SoftwareSystem
14 | lateinit var mainApp: Container
15 | lateinit var redis: Container
16 | lateinit var activeJob: Container
17 | lateinit var db: Container
18 |
19 | override fun defineModelEntities(model: Model) {
20 |
21 | microservice = model.addSoftwareSystem(
22 | "Complexity of Need Microservice",
23 | "A microservice for Complexity of Need Levels"
24 | )
25 |
26 | mainApp = microservice.addContainer(
27 | "Complexity of Need Microservice",
28 | "Main application component which holds the primary business logic. " +
29 | "Exposes a REST API for mastered data.",
30 | "Ruby on Rails"
31 | ).apply {
32 | Tags.DOMAIN_API.addTo(this)
33 | Tags.AREA_PRISONS.addTo(this)
34 | setUrl("https://github.com/ministryofjustice/hmpps-complexity-of-need")
35 | CloudPlatform.kubernetes.add(this)
36 | }
37 |
38 | db = microservice.addContainer(
39 | "CoN database",
40 | "Persistent storage of mastered data items and ancilliary data required for processing",
41 | "AWS RDS PostgreSQL"
42 | ).apply {
43 | Tags.DATABASE.addTo(this)
44 | CloudPlatform.rds.add(this)
45 | }
46 |
47 | redis = microservice.addContainer(
48 | "In-memory data store",
49 | "handles processing queues for event distribution",
50 | "REDIS"
51 | ).apply {
52 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
53 | CloudPlatform.elasticache.add(this)
54 | }
55 |
56 | activeJob = microservice.addContainer(
57 | "Queue Processor",
58 | "asynchronously processes the REDIS queue",
59 | "Ruby on Rails"
60 | ).apply {
61 | CloudPlatform.kubernetes.add(this)
62 | }
63 | }
64 |
65 | override fun defineRelationships() {
66 |
67 | mainApp.uses(HMPPSAuth.system, "authenticates access using")
68 |
69 | mainApp.uses(
70 | db,
71 | "stores mastered information",
72 | "JDBC"
73 | )
74 |
75 | mainApp.uses(
76 | redis,
77 | "places jobs on the queue"
78 | )
79 |
80 | activeJob.uses(
81 | redis,
82 | "consumes and processes the queue"
83 | )
84 |
85 | activeJob.uses(
86 | HMPPSDomainEvents.topic,
87 | "post events for other services to consume"
88 | )
89 | }
90 |
91 | override fun defineViews(views: ViewSet) {
92 |
93 | views.createSystemContextView(
94 | microservice,
95 | "complexity-of-need-context",
96 | "Context overview of the Complexity of Need microservice"
97 | ).apply {
98 | addDefaultElements()
99 | addNearestNeighbours(softwareSystem)
100 | enableAutomaticLayout()
101 | }
102 |
103 | views.createDeploymentView(
104 | microservice,
105 | "complexity-of-need-deployment",
106 | "Deployment overview of the Complexity of Need microservice"
107 | ).apply {
108 | add(AWS.london)
109 | removeRelationshipsNotConnectedToElement(microservice)
110 | enableAutomaticLayout()
111 | }
112 |
113 | views.createContainerView(
114 | microservice,
115 | "complexity-of-need-container",
116 | "Container overview of the Complexity of Need microservice"
117 | ).apply {
118 | addDefaultElements()
119 | setExternalSoftwareSystemBoundariesVisible(true)
120 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ConsiderARecall.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.Tags
11 |
12 | class ConsiderARecall private constructor() {
13 | companion object : HMPPSSoftwareSystem {
14 | lateinit var system: SoftwareSystem
15 | lateinit var ppcsCaseWorker: Person
16 | lateinit var considerARecallUi: Container
17 | lateinit var considerARecallApi: Container
18 | lateinit var db: Container
19 | lateinit var redis: Container
20 |
21 | override fun defineModelEntities(model: Model) {
22 |
23 | system = model.addSoftwareSystem(
24 | "Consider A Recall",
25 | "A service to help making decisions around recalls. Previously called Make A Recall Decision."
26 | )
27 |
28 | db = system.addContainer(
29 | "Consider A Recall API Database",
30 | "Database to store recalls caseworking info",
31 | "RDS Postgres DB"
32 | ).apply {
33 | Tags.DATABASE.addTo(this)
34 | CloudPlatform.rds.add(this)
35 | }
36 |
37 | redis = system.addContainer(
38 | "Redis Cache",
39 | "Cache of in-flight recalls",
40 | "Elasticache/Redis"
41 | ).apply {
42 | Tags.DATABASE.addTo(this)
43 | CloudPlatform.elasticache.add(this)
44 | }
45 |
46 | considerARecallApi = system.addContainer(
47 | "Consider A Recall API",
48 | "REST API for the Consider A Recall service",
49 | "Kotlin Spring Boot App"
50 | ).apply {
51 | Tags.DOMAIN_API.addTo(this)
52 | Tags.AREA_PROBATION.addTo(this)
53 | setUrl("https://github.com/ministryofjustice/make-recall-decision-api")
54 | CloudPlatform.kubernetes.add(this)
55 | }
56 |
57 | considerARecallUi = system.addContainer(
58 | "Consider A Recall Web Application",
59 | "Web application for the Consider A Recall service",
60 | "Node Express app"
61 | ).apply {
62 | setUrl("https://github.com/ministryofjustice/make-recall-decision-ui")
63 | Tags.WEB_BROWSER.addTo(this)
64 | CloudPlatform.kubernetes.add(this)
65 | }
66 | }
67 |
68 | override fun defineRelationships() {
69 | considerARecallApi.uses(db, "queries", "JDBC")
70 | considerARecallApi.uses(Delius.offenderSearch, "searches for offender")
71 | considerARecallApi.uses(Delius.MRDIntegrationService, "retrieves offender information", "REST+HTTP")
72 | considerARecallApi.uses(AssessRisksAndNeeds.riskNeedsService, "retrieves risk information", "REST+HTTP")
73 | considerARecallApi.uses(HMPPSDomainEvents.topic, "publishes finalised decisions events to", "SNS")
74 | considerARecallApi.uses(CreateAndVaryALicence.createAndVaryALicenceApi, "retrieves licence information", "REST+HTTP")
75 |
76 | considerARecallUi.uses(HMPPSAudit.system, "records user interactions", "HTTPS")
77 | considerARecallUi.uses(redis, "records in-flight recall decisions", "HTTPS")
78 | considerARecallUi.uses(considerARecallApi, "records user interactions", "HTTPS")
79 | considerARecallUi.uses(considerARecallApi, "operates on", "HTTPS")
80 | ProbationPractitioners.nps.uses(considerARecallUi, "Reviews information on offender, records recall decision")
81 | ProbationPractitioners.nps.uses(HMPPSAuth.system, "Authenticates via")
82 | }
83 |
84 | override fun defineViews(views: ViewSet) {
85 | views.createSystemContextView(
86 | system,
87 | "consider-a-recall-context",
88 | null
89 | ).apply {
90 | addDefaultElements()
91 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
92 | }
93 |
94 | views.createContainerView(
95 | system,
96 | "consider-a-recall-container",
97 | null
98 | ).apply {
99 | addDefaultElements()
100 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
101 | }
102 |
103 | views.createDeploymentView(ConsiderARecall.system, "consider-a-recall-deployment", "Deployment overview of the Consider A Recall service").apply {
104 | add(AWS.london)
105 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CourtRegister.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class CourtRegister private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Court register",
18 | "Provides and creates appointment information about prisoners"
19 | ).apply {
20 | Tags.DOMAIN_API.addTo(this)
21 | Tags.AREA_PROBATION.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/court-register"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for court information", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | }
38 |
39 | override fun defineViews(views: ViewSet) {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CourtUsers.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class CourtUsers private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var courtAdministrator: Person
11 |
12 | override fun defineModelEntities(model: Model) {
13 | courtAdministrator = model.addPerson("NPS court administrator")
14 | }
15 |
16 | override fun defineRelationships() {
17 | }
18 |
19 | override fun defineViews(views: ViewSet) {
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/CreateAndVaryALicence.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.Notify
11 | import uk.gov.justice.hmpps.architecture.annotations.Tags
12 |
13 | class CreateAndVaryALicence private constructor() {
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var system: SoftwareSystem
16 | lateinit var ppcsCaseWorker: Person
17 | lateinit var createAndVaryALicenceUi: Container
18 | lateinit var createAndVaryALicenceApi: Container
19 | lateinit var gotenburg: Container
20 | lateinit var storage: Container
21 | lateinit var db: Container
22 |
23 | override fun defineModelEntities(model: Model) {
24 |
25 | system = model.addSoftwareSystem(
26 | "Create And Vary A Licence",
27 | "A service to help create and manage licences."
28 | )
29 |
30 | db = system.addContainer(
31 | "Create And Vary A Licence API Database",
32 | "Database to store licence data",
33 | "RDS Postgres DB"
34 | ).apply {
35 | Tags.DATABASE.addTo(this)
36 | CloudPlatform.rds.add(this)
37 | }
38 |
39 | gotenburg = system.addContainer(
40 | "Gotenburg PDF Generator",
41 | "Generates PDFs of licences from data",
42 | "Java"
43 | ).apply {
44 | CloudPlatform.kubernetes.add(this)
45 | }
46 |
47 | storage = system.addContainer(
48 | "Assessments storage",
49 | "Storage for PDFs of licences",
50 | "S3"
51 | ).apply {
52 | Tags.DATABASE.addTo(this)
53 | CloudPlatform.s3.add(this)
54 | }
55 |
56 | createAndVaryALicenceApi = system.addContainer(
57 | "Create And Vary A Licence API",
58 | "REST API for the Create And Vary A Licence service",
59 | "Kotlin Spring Boot App"
60 | ).apply {
61 | Tags.DOMAIN_API.addTo(this)
62 | Tags.AREA_PROBATION.addTo(this)
63 | setUrl("https://github.com/ministryofjustice/create-and-vary-a-licence-api")
64 | CloudPlatform.kubernetes.add(this)
65 | }
66 |
67 | createAndVaryALicenceUi = system.addContainer(
68 | "Create And Vary A Licence Web Application",
69 | "Web application for the Create And Vary A Licence service",
70 | "Node Express app"
71 | ).apply {
72 | setUrl("https://github.com/ministryofjustice/create-and-vary-a-licence")
73 | Tags.WEB_BROWSER.addTo(this)
74 | CloudPlatform.kubernetes.add(this)
75 | }
76 | }
77 |
78 | override fun defineRelationships() {
79 | createAndVaryALicenceApi.uses(db, "queries", "JDBC")
80 |
81 | createAndVaryALicenceUi.uses(PrisonRegister.api, "gets prison details")
82 | createAndVaryALicenceUi.uses(NOMIS.offenderSearch, "searches for a prisoner")
83 | createAndVaryALicenceUi.uses(NOMIS.prisonApi, "gets sentence details")
84 | createAndVaryALicenceUi.uses(Delius.offenderSearch, "searches for offender")
85 | createAndVaryALicenceUi.uses(Delius.communityApi, "gets staff caseload")
86 | createAndVaryALicenceUi.uses(gotenburg, "creates pdfs")
87 | createAndVaryALicenceUi.uses(storage, "stores pdfs for later retrieval")
88 |
89 | createAndVaryALicenceApi.uses(HMPPSDomainEvents.topic, "publishes events to", "SNS")
90 | createAndVaryALicenceApi.uses(Notify.system, "sends notifications via")
91 |
92 | ProbationPractitioners.nps.uses(createAndVaryALicenceUi, "Create, vary and view licences")
93 | ProbationPractitioners.nps.uses(HMPPSAuth.system, "Authenticates via")
94 |
95 | createAndVaryALicenceUi.uses(createAndVaryALicenceApi, "operates on", "HTTPS")
96 | }
97 |
98 | override fun defineViews(views: ViewSet) {
99 | views.createSystemContextView(
100 | system,
101 | "create-and-vary-a-licence-context",
102 | null
103 | ).apply {
104 | addDefaultElements()
105 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
106 | }
107 |
108 | views.createContainerView(
109 | system,
110 | "create-and-vary-a-licence-container",
111 | null
112 | ).apply {
113 | addDefaultElements()
114 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
115 | }
116 |
117 | views.createDeploymentView(ConsiderARecall.system, "create-and-vary-a-licence-deployment", "Deployment overview of the Create And Vary A Licence service").apply {
118 | add(AWS.london)
119 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Curious.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.OutsideHMPPS
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class Curious private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var DPS: SoftwareSystem
15 | lateinit var curiousApi: Container
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem(
19 | "Curious",
20 | "(Contract management system for prison education providers"
21 | ).apply {
22 | OutsideHMPPS.addTo(this)
23 | Azure.sequation.add(this)
24 | }
25 | curiousApi = system.addContainer(
26 | "Curious API",
27 | "API over the Curious application - a contract management system for Education Providers", "Java"
28 | ).apply {
29 | Tags.DOMAIN_API.addTo(this)
30 | Tags.AREA_PRISONS.addTo(this)
31 | url = "https://github.com/ministryofjustice/curious-API"
32 | }
33 |
34 | DPS = model.addSoftwareSystem(
35 | "DPS - used by Digital Prison team applications and services",
36 | "HTTPS"
37 | ).apply {
38 | uses(curiousApi, "connects to", "RestHTML")
39 | }
40 | }
41 | override fun defineRelationships() {
42 | }
43 |
44 | override fun defineViews(views: ViewSet) {
45 | views.createContainerView(system, "curiouscontainer", null).apply {
46 | addDefaultElements()
47 | enableAutomaticLayout()
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/DigitalPrisonServices.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 |
9 | class DigitalPrisonServices private constructor() {
10 |
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var model: Model
13 | lateinit var system: SoftwareSystem
14 | lateinit var mainApp: Container
15 | lateinit var keyworkerUi: Container
16 | lateinit var restrictedPatientsUi: Container
17 | lateinit var adjudicationsUi: Container
18 |
19 | override fun defineModelEntities(model: Model) {
20 | this.model = model
21 |
22 | system = model.addSoftwareSystem(
23 | "Digital Prison Services",
24 | """
25 | Gives Prison Staff access to the data and operation they need on a day-to-day basis. Contains a subset of the functionality in C-NOMIS.
26 | """.trimIndent()
27 | )
28 |
29 | keyworkerUi = system.addContainer("Manage Key Workers", "Web app that contains functionality related to key workers").apply {
30 | setUrl("https://github.com/ministryofjustice/manage-key-workers")
31 | }
32 |
33 | restrictedPatientsUi = system.addContainer("Manage Restricted Patients", "Web app that contains functionality related to restricted patients").apply {
34 | setUrl("https://github.com/ministryofjustice/hmpps-restricted-patients")
35 | }
36 |
37 | adjudicationsUi = system.addContainer("Manage Adjudications", "Web app that contains functionality to report and view Adjudications").apply {
38 | setUrl("https://github.com/ministryofjustice/hmpps-manage-adjudications")
39 | }
40 |
41 | mainApp = system.addContainer("Digital Prison Services", "The web app that contains the main features").apply {
42 | setUrl("https://github.com/ministryofjustice/digital-prison-services")
43 | uses(keyworkerUi, "Provides links to")
44 | uses(restrictedPatientsUi, "Provides links to")
45 | uses(adjudicationsUi, "Provides links to")
46 | }
47 | }
48 |
49 | override fun defineRelationships() {
50 | mainApp.uses(NOMIS.prisonApi, "lookup visits, canteen, etc.")
51 | mainApp.uses(NOMIS.offenderSearch, "Retrieves prisoner data from")
52 | mainApp.uses(HMPPSAuth.system, "HTTPS Rest API")
53 | mainApp.uses(TokenVerificationApi.api, "validates API tokens via", "HTTPS Rest API")
54 | mainApp.uses(WhereaboutsApi.api, "HTTPS Rest API")
55 |
56 | keyworkerUi.uses(NOMIS.prisonApi, "lookup visits, canteen, etc.")
57 | keyworkerUi.uses(HMPPSAuth.system, "HTTPS Rest API")
58 | keyworkerUi.uses(TokenVerificationApi.api, "validates API tokens via", "HTTPS Rest API")
59 | keyworkerUi.uses(KeyworkerApi.api, "HTTPS Rest API")
60 | keyworkerUi.uses(ComplexityOfNeed.mainApp, "retrieves complexity of needs information from")
61 |
62 | restrictedPatientsUi.uses(NOMIS.prisonApi, "lookup visits, canteen, etc.")
63 | restrictedPatientsUi.uses(NOMIS.offenderSearch, "Retrieves prisoner data from")
64 | restrictedPatientsUi.uses(HMPPSAuth.system, "HTTPS Rest API")
65 | restrictedPatientsUi.uses(TokenVerificationApi.api, "validates API tokens via", "HTTPS Rest API")
66 | restrictedPatientsUi.uses(RestrictedPatientsApi.api, "HTTPS Rest API")
67 |
68 | adjudicationsUi.uses(NOMIS.prisonApi, "lookup visits, canteen, etc.")
69 | adjudicationsUi.uses(HMPPSAuth.system, "HTTPS Rest API")
70 | adjudicationsUi.uses(TokenVerificationApi.api, "validates API tokens via", "HTTPS Rest API")
71 | adjudicationsUi.uses(AdjudicationsApi.api, "HTTPS Rest API")
72 | }
73 |
74 | override fun defineViews(views: ViewSet) {
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/DigitalPrisonsNetwork.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 |
9 | class DigitalPrisonsNetwork private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var system: SoftwareSystem
12 | lateinit var identity: Container
13 |
14 | override fun defineModelEntities(model: Model) {
15 | system = model.addSoftwareSystem("Digital Prisons Network", "Provides in-cell devices and access to digital services to prisoners")
16 |
17 | identity = system.addContainer(
18 | "Prisoner Identity Service",
19 | "Azure Active Directory, supporting OAuth2 applications and network/device login",
20 | "Active Directory"
21 | )
22 | }
23 |
24 | override fun defineRelationships() {
25 | }
26 |
27 | override fun defineViews(views: ViewSet) {
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/EPF.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
10 |
11 | class EPF private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var projectManager: Person
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem(
18 | "EPF",
19 | "(Effective Proposal Framework) Presents sentencing options to NPS staff in court who are providing sentencing advice to sentencers"
20 | ).apply {
21 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
22 | }
23 |
24 | projectManager = model.addPerson("EPF Product Manager", "Product manager for the Effective Proposals Framework tool")
25 | }
26 |
27 | override fun defineRelationships() {
28 | projectManager.uses(system, "update intervention eligibility for accredited programmes in")
29 | projectManager.uses(system, "updates interventions table for discretionary services in")
30 | projectManager.interactsWith(PolicyTeams.sentencingPolicy, "listens to owners of interventions for changes in policy")
31 | ProbationPractitioners.nps.uses(system, "enters court, location, offender needs, assessment score data to receive a shortlist of recommended interventions for Pre-Sentence Report Proposal from")
32 | ProbationPractitioners.nps.uses(system, "enters location, offender needs, assessment score data to receive recommended interventions for licence condition planning from")
33 | InterventionTeams.interventionServicesTeam.interactsWith(projectManager, "provide programme suitability guide for accredited programme eligibility to")
34 | InterventionTeams.contractManagerForCRC.interactsWith(projectManager, "sends a signed off version of the CRC's Discretionary Services rate card brochure to")
35 | }
36 |
37 | override fun defineViews(views: ViewSet) {
38 | views.createSystemContextView(system, "epf-context", null).apply {
39 | addDefaultElements()
40 | addNearestNeighbours(projectManager)
41 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/EQuiP.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.AutomaticLayout
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
9 |
10 | class EQuiP private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 |
14 | override fun defineModelEntities(model: Model) {
15 | system = model.addSoftwareSystem(
16 | "EQuiP",
17 | "Central repository for all step-by-step business processes and policy documents in probation"
18 | ).apply {
19 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
20 | }
21 | }
22 |
23 | override fun defineRelationships() {
24 | PolicyTeams.sentencingPolicy.uses(system, "updates policy changes in")
25 | ProbationPractitioners.crc.uses(system, "finds information about a process or software in")
26 | ProbationPractitioners.nps.uses(system, "finds information about a process or software in")
27 | ProbationPractitioners.nps.uses(system, "finds rate cards in")
28 | }
29 |
30 | override fun defineViews(views: ViewSet) {
31 | views.createSystemContextView(system, "equip-context", null).apply {
32 | addDefaultElements()
33 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/HMPPSAPI.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class HMPPSAPI private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var system: SoftwareSystem
11 |
12 | override fun defineModelEntities(model: Model) {
13 |
14 | system = model.addSoftwareSystem(
15 | "HMPPS API",
16 | "HMPPS API Systems"
17 | )
18 | }
19 |
20 | override fun defineRelationships() {
21 | }
22 |
23 | override fun defineViews(views: ViewSet) {
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/HMPPSAudit.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class HMPPSAudit private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var app: Container
14 | lateinit var database: Container
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem("HMPPS Audit", "Stores audit records when business interactions occur")
18 |
19 | app = system.addContainer(
20 | "HMPPS Audit",
21 | "HMPPS Audit service",
22 | "Spring Boot + Kotlin"
23 | ).apply {
24 | Tags.REUSABLE_COMPONENT.addTo(this)
25 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
26 | url = "https://github.com/ministryofjustice/hmpps-audit"
27 | }
28 | }
29 |
30 | override fun defineRelationships() {
31 | }
32 |
33 | override fun defineViews(views: ViewSet) {
34 | views.createSystemContextView(system, "hmpps-audit-context", null).apply {
35 | addDefaultElements()
36 | enableAutomaticLayout()
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/HMPPSAuth.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Capability
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class HMPPSAuth private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var app: Container
15 | lateinit var database: Container
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem("HMPPS Auth", "Allows users to login into digital services")
19 |
20 | app = system.addContainer(
21 | "HMPPS Auth",
22 | "UI and OAuth2 server integrating with NOMIS database, nDelius (via community api) and an auth database for storing external users",
23 | "Spring Boot + Java"
24 | ).apply {
25 | Tags.WEB_BROWSER.addTo(this)
26 | Capability.IDENTITY.addTo(this)
27 | url = "https://github.com/ministryofjustice/hmpps-auth"
28 | }
29 |
30 | database = system.addContainer(
31 | "Internal Auth Database",
32 | "Holds explicit credentials, roles, multi-factor settings and banning data",
33 | "Microsoft SQL Server"
34 | ).apply {
35 | Tags.DATABASE.addTo(this)
36 | }
37 |
38 | app.uses(database, "connects to")
39 | }
40 |
41 | override fun defineRelationships() {
42 | app.uses(NOMIS.db, "authenticates via")
43 | app.uses(Delius.communityApi, "authenticates via")
44 | app.uses(AzureADTenantJusticeUK.directory, "authenticates via")
45 | app.uses(TokenVerificationApi.api, "stores tokens in")
46 | }
47 |
48 | override fun defineViews(views: ViewSet) {
49 | views.createSystemContextView(system, "hmpps-auth-context", null).apply {
50 | addDefaultElements()
51 | enableAutomaticLayout()
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/HMPPSDomainEvents.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class HMPPSDomainEvents private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var topic: SoftwareSystem
12 |
13 | override fun defineModelEntities(model: Model) {
14 |
15 | topic = model.addSoftwareSystem(
16 | "hmpps-domain-events",
17 | "Domain-wide Event Topic implemented with AWS Simple Notification Service"
18 | )
19 | .apply {
20 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
21 | Tags.TOPIC.addTo(this)
22 | }
23 | }
24 |
25 | override fun defineRelationships() {}
26 | override fun defineViews(views: ViewSet) {}
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Heroku.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.DeploymentNode
4 | import com.structurizr.model.Model
5 |
6 | class Heroku private constructor() {
7 | companion object {
8 | lateinit var dyno: DeploymentNode
9 | lateinit var database: DeploymentNode
10 |
11 | fun defineDeploymentNodes(model: Model) {
12 | val heroku = model.addDeploymentNode("Heroku", "Platform as a Service", "Heroku")
13 | dyno = heroku.addDeploymentNode(
14 | "Dyno",
15 | "Dynos are isolated, virtualized Linux containers that are designed to execute code based on a user-specified command",
16 | "Heroku"
17 | )
18 | database = heroku.addDeploymentNode("Managed Database", "Managed database services", "Heroku")
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/IM.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
10 | import uk.gov.justice.hmpps.architecture.annotations.Tags
11 |
12 | class IM private constructor() {
13 | companion object : HMPPSSoftwareSystem {
14 | lateinit var system: SoftwareSystem
15 | lateinit var i2nTeam: Person
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem(
19 | "Interventions Manager",
20 | "(IM) Used to schedule accredited programmes and record service users' progress on them"
21 | ).apply {
22 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
23 | }
24 |
25 | i2nTeam = model.addPerson(
26 | "i2n (formerly Northgate)",
27 | "Provides and maintains the Intervention Manager service"
28 | ).apply {
29 | addProperty("previous-name", "Northgate")
30 | Tags.PROVIDER.addTo(this)
31 | }
32 | }
33 |
34 | override fun defineRelationships() {
35 | system.uses(Delius.system, "pushes service user contact information to", "IAPS")
36 |
37 | InterventionTeams.npsTreatmentManager.uses(system, "schedules accredited programme appointments, tracks service user attendance and evaluate service user progress in")
38 | InterventionTeams.crcTreatmentManager.uses(system, "schedules accredited programme appointments, tracks service user attendance and evaluate service user progress in")
39 | i2nTeam.uses(system, "creates new accredited programmes in")
40 |
41 | Delius.supportTeam.interactsWith(i2nTeam, "notify an accredited programme is updated", "email")
42 | }
43 |
44 | override fun defineViews(views: ViewSet) {
45 | views.createSystemContextView(system, "interventions-manager-context", null).apply {
46 | addDefaultElements()
47 | add(i2nTeam)
48 | add(Delius.supportTeam)
49 | add(InterventionTeams.interventionServicesTeam)
50 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/InterventionTeams.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class InterventionTeams private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var interventionServicesTeam: Person
12 | lateinit var npsTreatmentManager: Person
13 | lateinit var contractManagerForCRC: Person
14 |
15 | lateinit var crcTreatmentManager: Person
16 | lateinit var crcProgrammeManager: Person
17 |
18 | lateinit var crsProvider: Person
19 |
20 | override fun defineModelEntities(model: Model) {
21 | interventionServicesTeam = model.addPerson(
22 | "Intervention Services Team",
23 | "They accredit intervention programmes and do business development of the interventions"
24 | )
25 | npsTreatmentManager = model.addPerson("NPS treatment manager")
26 | contractManagerForCRC = model.addPerson("Contract Manager for CRCs")
27 |
28 | crcTreatmentManager = model.addPerson("CRC treatment manager")
29 | .apply { Tags.PROVIDER.addTo(this) }
30 |
31 | crcProgrammeManager = model.addPerson(
32 | "CRC programme manager",
33 | "People who provide interventions on behalf of Community Rehabilitation Companies"
34 | ).apply { Tags.PROVIDER.addTo(this) }
35 |
36 | crsProvider = model.addPerson(
37 | "Commissioned rehabilitative services (CRS) provider",
38 | "Contracted providers delivering rehabilitation and resettlement interventions for service users"
39 | ).apply {
40 | Tags.PROVIDER.addTo(this)
41 | }
42 |
43 | crcProgrammeManager.interactsWith(contractManagerForCRC, "sends rate card brochure to")
44 | }
45 |
46 | override fun defineRelationships() {
47 | }
48 |
49 | override fun defineViews(views: ViewSet) {
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/KeyworkerApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class KeyworkerApi private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Keyworker API",
18 | "Provides and creates information about keyworkers of prisoners"
19 | ).apply {
20 | Tags.DOMAIN_API.addTo(this)
21 | Tags.AREA_PRISONS.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/keyworker-api"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for keyworker information", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | api.uses(NOMIS.prisonApi, "retrieves and creates keyworker information in")
38 | api.uses(ComplexityOfNeed.mainApp, "retrieves complexity of needs information from")
39 | }
40 |
41 | override fun defineViews(views: ViewSet) {
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Licences.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class Licences private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var system: SoftwareSystem
11 |
12 | override fun defineModelEntities(model: Model) {
13 | system = model.addSoftwareSystem("Licences", "This system is a stub. Please help us expand it.")
14 | }
15 |
16 | override fun defineRelationships() {
17 | system.uses(ProbationTeamsService.api, "retrieves and updates probation areas and Local Delivery Unit 'functional' mailboxes in")
18 | }
19 |
20 | override fun defineViews(views: ViewSet) {
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ManageASupervision.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.Notifier
11 | import uk.gov.justice.hmpps.architecture.annotations.OutsideHMPPS
12 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
13 |
14 | class ManageASupervision private constructor() {
15 | companion object : HMPPSSoftwareSystem {
16 | lateinit var system: SoftwareSystem
17 | lateinit var serviceUser: Person
18 | lateinit var manageASupervisionUi: Container
19 |
20 | override fun defineModelEntities(model: Model) {
21 | serviceUser = model.addPerson("Service User", "A person").apply {
22 | OutsideHMPPS.addTo(this)
23 | }
24 |
25 | system = model.addSoftwareSystem(
26 | "Manage A Supervision",
27 | "Digital service to support probation practitioners in managing " +
28 | "probation supervisions, including viewing service user history " +
29 | "and booking appointments."
30 | ).apply {
31 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
32 | }
33 |
34 | manageASupervisionUi = system.addContainer(
35 | "Manage A Supervision UI",
36 | "Microlith web application, displaying service user history and handling appointment booking",
37 | "Typescript, Node, NestJS, Express"
38 | ).apply {
39 | setUrl("https://github.com/ministryofjustice/hmpps-manage-supervisions")
40 | CloudPlatform.kubernetes.add(this)
41 | }
42 | }
43 |
44 | override fun defineRelationships() {
45 | ProbationPractitioners.nps.uses(manageASupervisionUi, "manages supervisions with")
46 | serviceUser.interactsWith(ProbationPractitioners.nps, "Meets with")
47 |
48 | manageASupervisionUi.uses(HMPPSAuth.system, "authenticates via")
49 | manageASupervisionUi.uses(Delius.communityApi, "Gets SU and past-offence details from")
50 | manageASupervisionUi.uses(AssessRisksAndNeeds.riskNeedsService, "Fetches risk and criminogenic need data from")
51 |
52 | Notifier.delivers(
53 | manageASupervisionUi,
54 | listOf(
55 | Triple(listOf(ProbationPractitioners.nps), "sends booked appointment details to", "email"),
56 | Triple(listOf(serviceUser), "sends appointment reminders to", "email and SMS")
57 | )
58 | )
59 | }
60 |
61 | override fun defineViews(views: ViewSet) {
62 | views.createSystemContextView(
63 | system,
64 | "manage-a-supervision-context", null
65 | ).apply {
66 | addDefaultElements()
67 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
68 | }
69 |
70 | views.createContainerView(system, "manage-a-supervision-container", null).apply {
71 | addDefaultElements()
72 | addAllContainersAndInfluencers()
73 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
74 | }
75 |
76 | views.createDeploymentView(system, "manage-a-supervision-deployment", "The Production deployment scenario for the Manage A Supervision service").apply {
77 | add(AWS.london)
78 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
79 | }
80 |
81 | views.createDynamicView(system, "manage-a-supervision-dynamic", "Data flow for Manage a Supervision").apply {
82 | // Login
83 | add(ProbationPractitioners.nps, "Submits login credentials to", HMPPSAuth.system)
84 | add(HMPPSAuth.system, "Provides authentication token to", ProbationPractitioners.nps)
85 | add(ProbationPractitioners.nps, "Authenticates future actions with token", manageASupervisionUi)
86 | // Display profile
87 | add(ProbationPractitioners.nps, "Selects a service user", manageASupervisionUi)
88 | add(manageASupervisionUi, "fetches service user personal details", Delius.communityApi)
89 | add(manageASupervisionUi, "fetches service user contact history", Delius.communityApi)
90 | add(manageASupervisionUi, "fetches service user risk scores/flags", AssessRisksAndNeeds.riskNeedsService)
91 | add(manageASupervisionUi, "displays service user profile to", ProbationPractitioners.nps)
92 | // Booking appointments
93 | add(ProbationPractitioners.nps, "Enters appointment details", manageASupervisionUi)
94 | add(manageASupervisionUi, "creates individual appointment records (contacts) with", Delius.communityApi)
95 | add(manageASupervisionUi, "sends notification to", ProbationPractitioners.nps)
96 | add(manageASupervisionUi, "sends notification to", serviceUser)
97 | // Recording outcomes
98 | add(ProbationPractitioners.nps, "Records appointment outcome", manageASupervisionUi)
99 | add(manageASupervisionUi, "updates contact in NDelius via", Delius.communityApi)
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ManagePOMCases.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.Notifier
11 | import uk.gov.justice.hmpps.architecture.annotations.Tags
12 |
13 | class ManagePOMCases private constructor() {
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var system: SoftwareSystem
16 | lateinit var ldu: Person
17 | lateinit var pom: Person
18 | lateinit var homd: Person
19 | lateinit var allocationManager: Container
20 | lateinit var redis: Container
21 | lateinit var activeJob: Container
22 | lateinit var db: Container
23 |
24 | override fun defineModelEntities(model: Model) {
25 |
26 | system = model.addSoftwareSystem(
27 | "Manage POM Cases",
28 | "A service for handling the handover of offenders from prison to probation"
29 | )
30 |
31 | ldu = model.addPerson(
32 | "Local Delivery Unit",
33 | "A Community/Probation team handling offenders who hail from a defined geographical area"
34 | )
35 |
36 | pom = model.addPerson(
37 | "Prison Offender Manager (POM)",
38 | "A prison staff member allocated to the prisoner"
39 | )
40 |
41 | homd = model.addPerson(
42 | "Head Of Offender Management Delivery (HOMD)",
43 | "A senior staff member who manages the prison's POM team"
44 | )
45 |
46 | allocationManager = system.addContainer(
47 | "Offender Management Allocation Manager",
48 | "Main Manage POM Cases (MPC) application component which holds the UI and primary business logic. " +
49 | "Not all mastered data is currently available from the API. Contact the project team " +
50 | "if further fields are required.",
51 | "Ruby on Rails"
52 | ).apply {
53 | Tags.DOMAIN_API.addTo(this)
54 | Tags.AREA_PRISONS.addTo(this)
55 | setUrl("https://github.com/ministryofjustice/offender-management-allocation-manager")
56 | CloudPlatform.kubernetes.add(this)
57 | }
58 |
59 | db = system.addContainer(
60 | "MPC database",
61 | "Database holding data mastered by MPC",
62 | "AWS RDS PostgreSQL"
63 | ).apply {
64 | Tags.DATABASE.addTo(this)
65 | CloudPlatform.rds.add(this)
66 | }
67 |
68 | redis = system.addContainer(
69 | "In-memory data store",
70 | "handles processing queues for email distribution and NOMIS movements",
71 | "REDIS"
72 | ).apply {
73 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
74 | CloudPlatform.elasticache.add(this)
75 | }
76 |
77 | activeJob = system.addContainer(
78 | "Queue Processor",
79 | "asynchronously processes the REDIS queue",
80 | "Ruby on Rails"
81 | ).apply {
82 | CloudPlatform.kubernetes.add(this)
83 | }
84 | }
85 |
86 | override fun defineRelationships() {
87 |
88 | pom.uses(system, "uses")
89 | homd.uses(system, "allocates POMs to offenders")
90 | homd.interactsWith(pom, "manages")
91 |
92 | allocationManager.uses(
93 | Delius.communityApi,
94 | "retrieves information on the prisoner's Community team, sends offender POM allocations",
95 | "REST"
96 | )
97 |
98 | allocationManager.uses(
99 | NOMIS.prisonApi,
100 | "retrieves prisoner information for prisons assigned to the logged in user",
101 | "REST"
102 | )
103 |
104 | allocationManager.uses(
105 | Delius.offenderSearch,
106 | "retrieves TIER & Mappa details for prisoners",
107 | "REST"
108 | )
109 |
110 | allocationManager.uses(
111 | NOMIS.offenderSearch,
112 | "retrieves the 'recall' status of offenders",
113 | "REST"
114 | )
115 |
116 | allocationManager.uses(HMPPSAuth.system, "authenticates users via")
117 |
118 | allocationManager.uses(
119 | db,
120 | "stores mastered information",
121 | "JDBC"
122 | )
123 |
124 | allocationManager.uses(
125 | redis,
126 | "places jobs on the queue"
127 | )
128 |
129 | activeJob.uses(
130 | redis,
131 | "consumes and processes the queue"
132 | )
133 |
134 | Notifier.delivers(
135 | activeJob,
136 | listOf(
137 | Triple(listOf(ldu), "notifies community allocation requests to", "email")
138 | )
139 | )
140 | }
141 |
142 | override fun defineViews(views: ViewSet) {
143 |
144 | views.createSystemContextView(
145 | system,
146 | "manage-POM-cases-context",
147 | "Context overview of the Manage POM Cases service"
148 | ).apply {
149 | addDefaultElements()
150 | addNearestNeighbours(softwareSystem)
151 | enableAutomaticLayout()
152 | }
153 |
154 | views.createDeploymentView(
155 | system,
156 | "manage-POM-cases-deployment",
157 | "Deployment overview of the Manage POM Cases service"
158 | ).apply {
159 | add(AWS.london)
160 | removeRelationshipsNotConnectedToElement(system)
161 | enableAutomaticLayout()
162 | }
163 |
164 | views.createContainerView(
165 | system,
166 | "manage-POM-cases-container",
167 | "Container overview of the Manage POM Cases Service"
168 | ).apply {
169 | addDefaultElements()
170 | setExternalSoftwareSystemBoundariesVisible(true)
171 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
172 | }
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/MoJSignOn.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class MoJSignOn private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var app: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem("MoJ SignOn", "Allows users to login into digital services")
17 |
18 | app = system.addContainer("MoJ SignOn Webapp", "OAuth based single sign on component designed to authorise staff access to multiple applications with a single account", "Ruby on Rails").apply {
19 | setUrl("https://github.com/ministryofjustice/moj-signon")
20 | Tags.WEB_BROWSER.addTo(this)
21 | Heroku.dyno.add(this)
22 | }
23 |
24 | val db = system.addContainer("Database", "Contains authentication and authorisation records", "PostgreSQL").apply {
25 | Tags.DATABASE.addTo(this)
26 | Heroku.database.add(this)
27 | }
28 |
29 | app.uses(db, "connects to")
30 | }
31 |
32 | override fun defineRelationships() {
33 | }
34 |
35 | override fun defineViews(views: ViewSet) {
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/NDH.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 |
9 | class NDH private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var system: SoftwareSystem
12 | private lateinit var initialSearch: Container
13 | private lateinit var offenderDetails: Container
14 | private lateinit var offenderAssessments: Container
15 | private lateinit var offenderRiskUpdates: Container
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem(
19 | "NDH",
20 | "(NOMIS Data Hub) Responsible for pulling/pushing data between HMPPS case management systems"
21 | )
22 |
23 | initialSearch = system.addContainer(
24 | "NDH offender initial search",
25 | "Used to identify if there are existing records in Delius for an offender",
26 | "endpoint"
27 | )
28 |
29 | offenderDetails = system.addContainer(
30 | "NDH offender details",
31 | "Retrieves data for a specific offender from either Delius or NOMIS and transforms it",
32 | "endpoint"
33 | )
34 |
35 | offenderAssessments = system.addContainer(
36 | "NDH offender assessments",
37 | "Sends risk assessment data from OASys to Delius",
38 | "endpoint"
39 | )
40 |
41 | offenderRiskUpdates = system.addContainer(
42 | "NDH offender risk updates",
43 | "Sends specific risk data from OAsys to Delius",
44 | "endpoint"
45 | )
46 | }
47 |
48 | override fun defineRelationships() {
49 | defineImpliedRelationships()
50 | defineDirectRelationships()
51 | }
52 |
53 | override fun defineViews(views: ViewSet) {
54 | }
55 |
56 | fun defineImpliedRelationships() {
57 | // these are the flows what NDH enables
58 | // it's very useful to see these relationships between NOMIS, OASys and Delius
59 | // without having to track it through a 'hub'
60 | NOMIS.system.uses(OASys.system, "offender details are copied into", "NDH")
61 | OASys.system.uses(NOMIS.system, "offender details are looked up from", "NDH")
62 | OASys.system.uses(Delius.system, "offender risk assessment and specific risk data are copied into", "NDH")
63 | OASys.system.uses(Delius.system, "offender details are looked up from", "NDH")
64 | }
65 |
66 | fun defineDirectRelationships() {
67 | system.uses(NOMIS.system, "periodically polls the NOMIS Events table to look for changes to offender data via", "Oracle XTAG queue")
68 | system.uses(OASys.system, "sends changed offender data collected from NOMIS via", "SOAP/XML")
69 |
70 | OASys.system.uses(initialSearch, "used during assessment process to identify if there are existing records in Delius for an offender via")
71 | initialSearch.uses(Delius.system, "searches for offender existence in")
72 |
73 | OASys.system.uses(offenderDetails, "used during assessment process to retrieve offender information from NOMIS or Delius via")
74 | offenderDetails.uses(Delius.system, "retrieves offender details from")
75 | offenderDetails.uses(NOMIS.system, "retrieves offender details from")
76 |
77 | OASys.system.uses(offenderAssessments, "used during certain points in the assessment process to receive risk assessment data from")
78 | offenderAssessments.uses(Delius.system, "sends risk assessment data to", "SOAP/XML/ActiveMQ")
79 |
80 | OASys.system.uses(offenderRiskUpdates, "used during certain points in the assessment process to receive specific risk data from")
81 | offenderRiskUpdates.uses(Delius.system, "sends specific risk data to", "SOAP/XML/ActiveMQ")
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/NID.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class NID private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 |
14 | override fun defineModelEntities(model: Model) {
15 | system = model.addSoftwareSystem(
16 | "NID",
17 | "(Deprecated) (National Intervention Database) Spreadsheet to store intervention details"
18 | ).apply {
19 | Tags.DEPRECATED.addTo(this)
20 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
21 | }
22 | }
23 |
24 | override fun defineRelationships() {
25 | }
26 |
27 | override fun defineViews(views: ViewSet) {
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/NOMIS.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class NOMIS private constructor() {
11 |
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var db: Container
15 | lateinit var offenderSearch: Container
16 | lateinit var custodyApi: Container
17 | lateinit var prisonApi: Container
18 | lateinit var elasticSearchStore: Container
19 |
20 | override fun defineModelEntities(model: Model) {
21 | system = model.addSoftwareSystem(
22 | "NOMIS",
23 | "(National Offender Management Information System) The case management system for offender data in use in custody - both public and private prisons"
24 | )
25 |
26 | db = system.addContainer("NOMIS database", null, "Oracle").apply {
27 | Tags.DATABASE.addTo(this)
28 | }
29 |
30 | custodyApi = system.addContainer(
31 | "Custody API (Deprecated)",
32 | "Provided REST access to the Nomis Oracle DB offender information - deprecated; use prison-api instead",
33 | "Java"
34 | ).apply {
35 | Tags.DATA_API.addTo(this)
36 | Tags.AREA_PRISONS.addTo(this)
37 | Tags.DEPRECATED.addTo(this)
38 | setUrl("https://github.com/ministryofjustice/custody-api")
39 | uses(db, "connects to", "JDBC")
40 | }
41 |
42 | prisonApi = system.addContainer(
43 | "Prison API",
44 | "API over the NOMIS DB used by Digital Prison team applications and services", "Java"
45 | ).apply {
46 | Tags.DATA_API.addTo(this)
47 | Tags.AREA_PRISONS.addTo(this)
48 | addProperty("previous-name", "Elite2 API")
49 | url = "https://github.com/ministryofjustice/prison-api"
50 | uses(db, "connects to", "JDBC")
51 | }
52 |
53 | system.addContainer(
54 | "DPS Web Application",
55 | "DPS - used by Digital Prison team applications and services",
56 | "HTTPS"
57 | ).apply {
58 | uses(prisonApi, "connects to", "RestHTML")
59 | uses(Curious.system, "Consumes")
60 | }
61 |
62 | elasticSearchStore = system.addContainer(
63 | "ElasticSearch store",
64 | "Data store for NOMIS content", "ElasticSearch"
65 | ).apply {
66 | Tags.DATABASE.addTo(this)
67 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
68 | CloudPlatform.elasticsearch.add(this)
69 | }
70 |
71 | offenderSearch = system.addContainer(
72 | "PrisonerSearch", "API over the NOMIS prisoner data held in Elasticsearch",
73 | "Kotlin"
74 | ).apply {
75 | Tags.DOMAIN_API.addTo(this)
76 | Tags.AREA_PRISONS.addTo(this)
77 | uses(elasticSearchStore, "Queries prisoner data from NOMIS Elasticsearch Index")
78 | setUrl("https://github.com/ministryofjustice/prisoner-offender-search")
79 | CloudPlatform.kubernetes.add(this)
80 | }
81 |
82 | system.addContainer(
83 | "Prison Offender Events",
84 | "Publishes Events about prisoner changes to Pub / Sub Topics.", "Kotlin"
85 | ).apply {
86 | setUrl("https://github.com/ministryofjustice/prison-offender-events")
87 | uses(db, "connects to", "JDBC")
88 | CloudPlatform.sqs.add(this)
89 | CloudPlatform.sns.add(this)
90 | CloudPlatform.kubernetes.add(this)
91 | }
92 | }
93 |
94 | override fun defineRelationships() {
95 | system.uses(PrisonToProbationUpdate.system, "notifies changes")
96 | }
97 |
98 | override fun defineViews(views: ViewSet) {
99 | views.createContainerView(system, "nomiscontainer", null).apply {
100 | addDefaultElements()
101 | enableAutomaticLayout()
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/NationalPrisonRadio.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class NationalPrisonRadio private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var system: SoftwareSystem
12 |
13 | override fun defineModelEntities(model: Model) {
14 | system = model.addSoftwareSystem(
15 | "National Prison Radio",
16 | "The national radio station for prisoners. Made by prisoners, for prisoners"
17 | ).apply {
18 | Tags.PROVIDER.addTo(this)
19 | }
20 | }
21 |
22 | override fun defineRelationships() {}
23 |
24 | override fun defineViews(views: ViewSet) {}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Notify.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.annotations.OutsideHMPPS
7 |
8 | class Notify private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var system: SoftwareSystem
11 |
12 | override fun defineModelEntities(model: Model) {
13 | system = model.addSoftwareSystem("GOV.UK Notify", "Centralized goverment notification system")
14 | .apply {
15 | OutsideHMPPS.addTo(this)
16 | }
17 | }
18 |
19 | override fun defineRelationships() {
20 | }
21 |
22 | override fun defineViews(views: ViewSet) {
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/OASys.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class OASys private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var arn: Container
15 | lateinit var assessmentsApi: Container
16 | lateinit var ORDSApi: Container
17 | lateinit var assessmentsEvents: Container
18 | lateinit var assessmentsUpdateApi: Container
19 | lateinit var oasysDB: Container
20 |
21 | override fun defineModelEntities(model: Model) {
22 | system = model.addSoftwareSystem("OASys", "(Offender Assessment System) Assesses the risks and needs of offenders").apply {
23 | url = "https://dsdmoj.atlassian.net/wiki/spaces/~474366104/pages/2046820357/OASys+Overview"
24 | }
25 |
26 | oasysDB = system.addContainer("OASys Database", null, "Oracle").apply {
27 | Tags.DATABASE.addTo(this)
28 | Azure.nomsdigitech.add(this)
29 | }
30 |
31 | assessmentsApi = system.addContainer("Offender Assessments API", "REST access to the OASYS Oracle DB offender assessment information", "Kotlin + Spring Boot").apply {
32 | Tags.DATA_API.addTo(this)
33 | Tags.AREA_PROBATION.addTo(this)
34 | uses(oasysDB, "connects to", "JDBC")
35 | url = "https://github.com/ministryofjustice/offender-assessments-api-kotlin"
36 | Azure.kubernetes.add(this)
37 | }
38 |
39 | ORDSApi = system.addContainer("ORDS API endpoints", "REST access to custom Oracle DB queries", "Java").apply {
40 | Tags.DATA_API.addTo(this)
41 | Tags.AREA_PROBATION.addTo(this)
42 | uses(oasysDB, "connects to", "JDBC")
43 | Azure.nomsdigitech.add(this)
44 | }
45 | }
46 |
47 | override fun defineRelationships() {
48 | ProbationPractitioners.nps.uses(system, "records offender risk (attendance, contact, etc.) and assessment in")
49 | assessmentsApi.uses(ORDSApi, "connects to", "REST")
50 | }
51 |
52 | override fun defineViews(views: ViewSet) {
53 | views.createSystemContextView(
54 | system,
55 | "OASYS-context", null
56 | ).apply {
57 | addDefaultElements()
58 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
59 | }
60 |
61 | views.createContainerView(system, "OASYS-container", null).apply {
62 | addDefaultElements()
63 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
64 | }
65 |
66 | views.createDeploymentView(OASys.system, "OASYS-deployment", "Deployment overview of OASys").apply {
67 | add(Azure.kubernetes)
68 | add(Azure.nomsdigitech)
69 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Pathfinder.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import uk.gov.justice.hmpps.architecture.annotations.Tags
7 |
8 | class Pathfinder(model: Model) {
9 | val system: SoftwareSystem
10 | val webApp: Container
11 |
12 | init {
13 | system = model.addSoftwareSystem(
14 | "Pathfinder",
15 | "The case management system for Pathfinder nominals"
16 | )
17 |
18 | val db = system.addContainer(
19 | "Pathfinder Database",
20 | "Database to store Pathfinder case management", "RDS Postgres DB"
21 | ).apply {
22 | Tags.DATABASE.addTo(this)
23 | CloudPlatform.rds.add(this)
24 | }
25 |
26 | webApp = system.addContainer(
27 | "Pathfinder Web Application",
28 | "Web application for the case management of Pathfinder nominals", "Node Express app"
29 | ).apply {
30 | Tags.WEB_BROWSER.addTo(this)
31 | uses(db, null)
32 | uses(NOMIS.prisonApi, "extract NOMIS offender data")
33 | uses(NOMIS.offenderSearch, "to search for prisoners")
34 | uses(Delius.communityApi, "extract nDelius offender data")
35 | uses(Delius.offenderSearch, "to search for offenders")
36 | CloudPlatform.kubernetes.add(this)
37 | }
38 |
39 | system.addContainer(
40 | "Pathfinder API",
41 | "API over the Pathfinder DB used by internal applications", "Kotlin Spring Boot App"
42 | ).apply {
43 | Tags.DOMAIN_API.addTo(this)
44 | Tags.AREA_PRISONS.addTo(this)
45 | Tags.AREA_PROBATION.addTo(this)
46 | setUrl("https://github.com/ministryofjustice/pathfinder-api")
47 | uses(db, "JDBC")
48 | CloudPlatform.kubernetes.add(this)
49 | }
50 |
51 | val pPrisonPreventLead = model.addPerson("Prison Prevent Lead", "They case manage Pathfinder Nominals in custody")
52 | pPrisonPreventLead.uses(webApp, "Visits pathfinder app to manage their local prison(s) caseload", "HTTPS")
53 | pPrisonPreventLead.uses(HMPPSAuth.system, "Login")
54 |
55 | val pRegionalPrisonPreventLead = model.addPerson("Regional Prison Prevent Lead", "They case manage a region of Pathfinder Nominals in custody")
56 | pRegionalPrisonPreventLead.uses(webApp, "Visits pathfinder app to manage their Regional prisons caseload", "HTTPS")
57 | pRegionalPrisonPreventLead.uses(HMPPSAuth.system, "Login")
58 |
59 | val pProbationOffenderManager = model.addPerson("Probation Offender Manager", "They case manage Pathfinder Nominals in the community")
60 | pProbationOffenderManager.uses(webApp, "Visits pathfinder app to manage their community caseload", "HTTPS")
61 | pProbationOffenderManager.uses(HMPPSAuth.system, "Login")
62 |
63 | val pRegionalPoliceUser = model.addPerson("Regional Police User", "They access limited regional data of Pathfinder Nominals")
64 | pRegionalPoliceUser.uses(webApp, "Visits pathfinder app to manage offenders in their region due for release", "HTTPS")
65 | pRegionalPoliceUser.uses(HMPPSAuth.system, "Login")
66 |
67 | val pNationalPoliceUser = model.addPerson("National Police User", "They access limited National data of Pathfinder Nominals")
68 | pNationalPoliceUser.uses(webApp, "Visits pathfinder app to manage offenders nationally due for release", "HTTPS")
69 | pNationalPoliceUser.uses(HMPPSAuth.system, "Login")
70 |
71 | val pNationalIntelligenceUnitUser = model.addPerson("National Prison User", "They have access to all Pathfinder nominals")
72 | pNationalIntelligenceUnitUser.uses(webApp, "Visits pathfinder app to manage the performance of the service", "HTTPS")
73 | pNationalIntelligenceUnitUser.uses(HMPPSAuth.system, "Login")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PolicyTeams.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class PolicyTeams private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var sentencingPolicy: Person
11 |
12 | override fun defineModelEntities(model: Model) {
13 | sentencingPolicy = model.addPerson("Sentencing Policy", "Pseudo-team to capture sentencing policy meeting participants")
14 | }
15 |
16 | override fun defineRelationships() {
17 | }
18 |
19 | override fun defineViews(views: ViewSet) {
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PrepareCaseForSentence.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Location
5 | import com.structurizr.model.Model
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.OutsideHMPPS
11 | import uk.gov.justice.hmpps.architecture.annotations.Tags
12 |
13 | class PrepareCaseForSentence private constructor() {
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var system: SoftwareSystem
16 | lateinit var prepareCaseUI: Container
17 | lateinit var courtCaseService: Container
18 | lateinit var courtCaseMatcher: Container
19 |
20 | override fun defineModelEntities(model: Model) {
21 | system = model.addSoftwareSystem(
22 | "Prepare a Case for Sentence",
23 | "Digital Service for Probation Officers working in magistrates' courts, " +
24 | "providing them with a single location to access the defendant information " +
25 | "they need to provide sound and timely sentencing guidance to magistrates"
26 | )
27 |
28 | val casesDb = system.addContainer(
29 | "Cases Database",
30 | "Holds court lists, cases and offender ids",
31 | "PostgreSQL"
32 | ).apply {
33 | Tags.DATABASE.addTo(this)
34 | CloudPlatform.rds.add(this)
35 | }
36 |
37 | val messagesBucket = system.addContainer(
38 | "Message Store",
39 | "Holds CPG messages data and meta-data",
40 | "S3"
41 | ).apply {
42 | Tags.DATABASE.addTo(this)
43 | CloudPlatform.s3.add(this)
44 | }
45 |
46 | val crimePortalGatewayQueue = system.addContainer(
47 | "crime-portal-gateway-queue",
48 | "Carries court list messages",
49 | "SQS"
50 | ).apply {
51 | Tags.QUEUE.addTo(this)
52 | CloudPlatform.sqs.add(this)
53 | }
54 |
55 | courtCaseService = system.addContainer(
56 | "Court Case Service",
57 | "Court case business logic and REST API consumed by the Prepare a Case web application",
58 | "Java + Spring Boot"
59 | ).apply {
60 | Tags.DOMAIN_API.addTo(this)
61 | Tags.AREA_PROBATION.addTo(this)
62 | uses(casesDb, "connects to", "JDBC")
63 | setUrl("https://github.com/ministryofjustice/court-case-service")
64 | }
65 |
66 | courtCaseMatcher = system.addContainer(
67 | "Court Case Matcher",
68 | "Consumes court lists from Crime Portal and matches defendants against the nDelius database via Offender Search",
69 | "Java + Spring Boot"
70 | ).apply {
71 | uses(courtCaseService, "Creates or updates cases in")
72 | uses(crimePortalGatewayQueue, "Consumes court list messages from")
73 | setUrl("https://github.com/ministryofjustice/court-case-matcher")
74 | }
75 |
76 | prepareCaseUI = system.addContainer(
77 | "Prepare a Case UI",
78 | "Web application which delivers HTML to the users browser",
79 | "Node + Express"
80 | ).apply {
81 | uses(courtCaseService, "View case defendant details and capture court judgements using ")
82 | setUrl("https://github.com/ministryofjustice/prepare-a-case")
83 | }
84 |
85 | val crimePortalGateway = system.addContainer(
86 | "Crime Portal Gateway",
87 | "SOAP web service which receives court lists from HMCTS and publishes onto an SQS message queue",
88 | "Kotlin + Spring Boot"
89 | ).apply {
90 | uses(crimePortalGatewayQueue, "Produces court list messages to")
91 | uses(messagesBucket, "stores messages in")
92 | setUrl("https://github.com/ministryofjustice/crime-portal-gateway")
93 | }
94 |
95 | // TODO refactor out HMCTS Crime Portal into dedicated SoftwareSystem
96 | model.addSoftwareSystem("HMCTS Crime Portal", "Case Management for HMCTS holding data relating to court cases")
97 | .apply {
98 | setLocation(Location.External)
99 | OutsideHMPPS.addTo(this)
100 | uses(crimePortalGateway, "Sends court lists to")
101 | }
102 | }
103 |
104 | override fun defineRelationships() {
105 | prepareCaseUI.uses(HMPPSAuth.system, "authenticates via")
106 | listOf(courtCaseService, courtCaseMatcher)
107 | .forEach { it.uses(HMPPSAuth.system, "validates API tokens via") }
108 |
109 | courtCaseService.uses(Delius.communityApi, "Gets offender details from")
110 | courtCaseMatcher.uses(Delius.offenderSearch, "Matches defendants to known offenders")
111 | courtCaseService.uses(OASys.assessmentsApi, "get offender assessment details from")
112 |
113 | prepareCaseUI.uses(UserPreferenceApi.api, "Stores user's preferred courts in", "HTTPS Rest API")
114 |
115 | CourtUsers.courtAdministrator.uses(prepareCaseUI, "prepares cases for sentencing")
116 | ProbationPractitioners.nps.uses(prepareCaseUI, "views case defendant details")
117 | }
118 |
119 | override fun defineViews(views: ViewSet) {
120 |
121 | views.createSystemContextView(system, "prepare-case-context", null).apply {
122 | addDefaultElements()
123 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
124 | }
125 |
126 | views.createContainerView(system, "prepare-case-container", null).apply {
127 | addDefaultElements()
128 | addAllContainersAndInfluencers()
129 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PrisonRegister.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class PrisonRegister private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Prison register",
18 | "Provides a reference data around prisons"
19 | )
20 |
21 | api = system.addContainer("Prison Register API", "API", "Kotlin + Spring Boot").apply {
22 | Tags.DOMAIN_API.addTo(this)
23 | Tags.AREA_PRISONS.addTo(this)
24 | url = "https://github.com/ministryofjustice/prison-register"
25 | }
26 |
27 | val db = system.addContainer("Database", "Storage for prison information", "PostgreSQL").apply {
28 | Tags.DATABASE.addTo(this)
29 | }
30 |
31 | api.uses(db, "connects to")
32 | }
33 |
34 | override fun defineRelationships() {
35 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
36 | }
37 |
38 | override fun defineViews(views: ViewSet) {
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PrisonToProbationUpdate.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 |
8 | class PrisonToProbationUpdate private constructor() {
9 | companion object : HMPPSSoftwareSystem {
10 | lateinit var system: SoftwareSystem
11 |
12 | override fun defineModelEntities(model: Model) {
13 | system = model.addSoftwareSystem(
14 | "Prison to Probation Update",
15 | "Listens for events from Prison systems (NOMIS) to update offender sentence information in Probation systems (Delius)"
16 | ).apply {
17 | setUrl("https://github.com/ministryofjustice/prison-to-probation-update")
18 | }
19 | }
20 |
21 | override fun defineRelationships() {
22 | system.uses(Delius.system, "update offender sentence information in")
23 | }
24 |
25 | override fun defineViews(views: ViewSet) {
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PrisonVisitsBooking.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class PrisonVisitsBooking private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | protected lateinit var frontend: Container
15 | protected lateinit var backend: Container
16 |
17 | override fun defineModelEntities(model: Model) {
18 | system = model.addSoftwareSystem(
19 | "Prison Visits Booking",
20 | "A service for booking a social visit to a prisoner in England or Wales"
21 | )
22 |
23 | frontend = system.addContainer("Frontend", "Public interface for booking a prison visit", "Ruby on Rails").apply {
24 | setUrl("https://github.com/ministryofjustice/prison-visits-public")
25 | Tags.WEB_BROWSER.addTo(this)
26 | CloudPlatform.kubernetes.add(this)
27 | }
28 |
29 | backend = system.addContainer("Prison Visits Booking Backend", "API for the frontend and admin interface for staff to manage bookings", "Ruby on Rails").apply {
30 | Tags.DOMAIN_API.addTo(this)
31 | Tags.AREA_PRISONS.addTo(this)
32 | setUrl("https://github.com/ministryofjustice/prison-visits-2")
33 | CloudPlatform.kubernetes.add(this)
34 | }
35 |
36 | val zendesk = system.addContainer("Customer service", "Handles feedback tickets raised by staff and members of the public", "Zendesk").apply {
37 | Tags.WEB_BROWSER.addTo(this)
38 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
39 | Tags.PROVIDER.addTo(this)
40 | }
41 |
42 | val sidekiq = system.addContainer("Sidekiq", "Listens to queued events and processes them", "Sidekiq").apply {
43 | CloudPlatform.kubernetes.add(this)
44 | }
45 |
46 | val queue = system.addContainer("Queue", "Key-value store used for scheduling jobs via Sidekiq", "Redis").apply {
47 | Tags.DATABASE.addTo(this)
48 | CloudPlatform.elasticache.add(this)
49 | }
50 |
51 | val db = system.addContainer("Database", "Bookings database", "PostgreSQL").apply {
52 | Tags.DATABASE.addTo(this)
53 | CloudPlatform.rds.add(this)
54 | }
55 |
56 | frontend.uses(backend, "books and retrieves bookings from", "HTTP")
57 | backend.uses(queue, "queues feedback jobs to")
58 | sidekiq.uses(queue, "processes queued jobs from")
59 | sidekiq.uses(zendesk, "raises feedback as tickets in")
60 | backend.uses(db, "connects to")
61 | }
62 |
63 | override fun defineRelationships() {
64 | backend.uses(MoJSignOn.app, "authenticates with", "HTTP")
65 | backend.uses(NOMIS.prisonApi, "retrieves prison visiting slots and prisoner visiting-related data from", "HTTP")
66 | }
67 |
68 | override fun defineViews(views: ViewSet) {
69 | views.createSystemContextView(system, "prison-visits-booking-context", null).apply {
70 | addDefaultElements()
71 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
72 | }
73 |
74 | views.createContainerView(system, "prison-visits-booking-container", null).apply {
75 | addDefaultElements()
76 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/PrisonerContentHub.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.OutsideHMPPS
10 | import uk.gov.justice.hmpps.architecture.annotations.Tags
11 |
12 | class PrisonerContentHub private constructor() {
13 |
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var model: Model
16 | lateinit var system: SoftwareSystem
17 | lateinit var contentHubFrontend: Container
18 | lateinit var frontendProxy: Container
19 |
20 | /**
21 | * TODO: add Prisoner internet infrastructure, AD
22 | * TODO: add BT PINS
23 | **/
24 | override fun defineModelEntities(model: Model) {
25 | this.model = model
26 |
27 | system = model.addSoftwareSystem(
28 | "Prisoner Content Hub",
29 | """
30 | The Prisoner Content Hub is a platform for prisoners to access data, content and services supporting individual progression and freeing up staff time.
31 | """.trimIndent()
32 | ).apply {
33 | val PRISON_PROBATION_PROPERTY_NAME = "business_unit"
34 | val PRISON_SERVICE = "prisons"
35 | addProperty(PRISON_PROBATION_PROPERTY_NAME, PRISON_SERVICE)
36 | }
37 |
38 | val elasticSearchStore = system.addContainer("ElasticSearch store", "Data store for feedback collection, and indexing for Drupal CMS content", "ElasticSearch").apply {
39 | Tags.DATABASE.addTo(this)
40 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
41 | CloudPlatform.elasticsearch.add(this)
42 | }
43 |
44 | val googleDataStudio = system.addContainer("Google Data Studio", "Reporting tool for Google Analytics and Feedback data", "Google Data Studio").apply {
45 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
46 | }
47 |
48 | val feedbackUploaderJob = system.addContainer("Feedback uploader job", "Runs cron to collate daily feedback data", "TypeScript").apply {
49 | uses(elasticSearchStore, "Extracts feedback data")
50 | uses(googleDataStudio, "Uploads feedback")
51 | CloudPlatform.kubernetes.add(this)
52 | }
53 |
54 | val drupalDatabase = system.addContainer("Drupal database", "Prisoner Content Hub CMS data and Drupal metadata", "MariaDB").apply {
55 | Tags.DATABASE.addTo(this)
56 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
57 | CloudPlatform.rds.add(this)
58 | }
59 |
60 | val s3ContentStore = system.addContainer("Content Store", "Audio, video, PDF and image content for the Prisoner Content Hub", "S3").apply {
61 | Tags.DATABASE.addTo(this)
62 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
63 | CloudPlatform.s3.add(this)
64 | }
65 |
66 | val googleAnalytics = system.addContainer("Google Analytics", "Tracking page views and user journeys", "Google Analytics").apply {
67 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
68 | }
69 |
70 | val drupal = system.addContainer("Prisoner Content Hub CMS", "Content Management System for HMPPS Digital and prison staff to curate content for prisoners", "Drupal").apply {
71 | setUrl("https://github.com/ministryofjustice/prisoner-content-hub-backend")
72 | uses(drupalDatabase, "MariaDB connection")
73 | uses(elasticSearchStore, "HTTPS REST API")
74 | uses(s3ContentStore, "HTTPS REST API")
75 | CloudPlatform.kubernetes.add(this)
76 | }
77 |
78 | contentHubFrontend = system.addContainer("Prisoner Content Hub frontend", "Prisoner-facing view of the Content Hub", "Node").apply {
79 | setUrl("https://github.com/ministryofjustice/prisoner-content-hub-frontend")
80 | uses(drupal, "HTTPS Rest API")
81 | uses(elasticSearchStore, "HTTPS REST API")
82 | uses(googleAnalytics, "pushes page events")
83 | CloudPlatform.kubernetes.add(this)
84 | }
85 |
86 | frontendProxy = system.addContainer("ICEcast streamer", "Proxy for media on external domains not accessible to prison users", "ICEcast").apply {
87 | CloudPlatform.kubernetes.add(this)
88 | }
89 |
90 | val kibanaDashboard = system.addContainer("Kibana dashboard", "Feedback reports and analytics dashboard", "Kibana").apply {
91 | // setUrl("TODO")
92 | uses(elasticSearchStore, "HTTPS Rest API")
93 | CloudPlatform.elasticsearch.add(this)
94 | }
95 |
96 | /**
97 | * Users
98 | **/
99 | model.addPerson("Content Hub Product Team", "Collates feedback for product development and analytics").apply {
100 | uses(googleDataStudio, "Views feedback entries")
101 | uses(googleAnalytics, "Views content hub frontend usage statistics")
102 | }
103 |
104 | model.addPerson("HMPPS local content manager", "Collates feedback for content and operational purposes").apply {
105 | uses(googleDataStudio, "Views report in Google Data Studio of prisoner feedback, views individual feedback responses, and analyses sentiment and statistics of feedback")
106 | }
107 |
108 | model.addPerson("Prisoner / Young Offender", "A person held in the public prison estate or a Young Offender Institute").apply {
109 | uses(contentHubFrontend, "Views videos, audio programmes, site updates, and rehabilitative material")
110 | uses(frontendProxy, "Listens to National Prison Radio live stream")
111 | uses(s3ContentStore, "Streams audio, video and uploaded media")
112 | OutsideHMPPS.addTo(this)
113 | }
114 |
115 | model.addPerson("Content editor", "HMPPS Digital staff curating content for the entire prison estate and supporting individual prisons").apply {
116 | uses(drupal, "Authors and curates content for the prison estate")
117 | }
118 |
119 | model.addPerson("Prison Content editor", "A content author on-site in a prison, authoring content for their prison").apply {
120 | uses(drupal, "Authors and curates content for their prison")
121 | }
122 | }
123 |
124 | override fun defineRelationships() {
125 | contentHubFrontend.uses(NOMIS.prisonApi, "lookup visits, canteen, etc.")
126 | contentHubFrontend.uses(DigitalPrisonsNetwork.identity, "Prisoner logs in with OAuth2 flow")
127 |
128 | frontendProxy.uses(NationalPrisonRadio.system, "proxy OGG livestream audio")
129 | }
130 |
131 | override fun defineViews(views: ViewSet) {
132 | views.createSystemContextView(
133 | system, "prisonerContentHubSystemContext", "The system context diagram for the Prisoner Content Hub"
134 | ).apply {
135 | addDefaultElements()
136 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
137 | }
138 |
139 | views.createContainerView(system, "prisonerContentHubContainer", "Prisoner Content Hub container view").apply {
140 | addDefaultElements()
141 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
142 | }
143 |
144 | views.createDeploymentView(system, "prisonerContentHubContainerProductionDeployment", "The Production deployment scenario for the Prisoner Content Hub").apply {
145 | add(AWS.london)
146 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ProbationAllocationTool.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class ProbationAllocationTool private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var ui: Container
15 | lateinit var allocationsApi: Container
16 | lateinit var allocationsDb: Container
17 |
18 | override fun defineModelEntities(model: Model) {
19 | system = model.addSoftwareSystem(
20 | "Probation Case Allocation tool",
21 | "Allows users to allocate probation practitioners to PoPs (People on Probation)"
22 | )
23 |
24 | ui = system.addContainer("Manage a Workforce UI", "UI for allocating cases", "Node").apply {
25 | url = "https://github.com/ministryofjustice/manage-a-workforce-ui"
26 | }
27 |
28 | allocationsApi = system.addContainer("Allocations API", "Provides information related to unallocated cases", "Kotlin").apply {
29 | url = "https://github.com/ministryofjustice/hmpps-allocations"
30 | Tags.DOMAIN_API.addTo(this)
31 | Tags.AREA_PROBATION.addTo(this)
32 | }
33 |
34 | allocationsDb = system.addContainer("Database", "Storage for current unallocated cases", "PostgreSQL").apply {
35 | Tags.DATABASE.addTo(this)
36 | }
37 | }
38 |
39 | override fun defineRelationships() {
40 | ProbationPractitioners.spo.uses(system, "find and allocates unallocated cases by looking at")
41 | system.uses(Delius.communityApi, "gets probation case data from")
42 | system.uses(OASys.system, "gets PoP risk data from")
43 |
44 | allocationsApi.uses(allocationsDb, "connects to")
45 | ui.uses(WMT.workloadApi, "connects to")
46 | ui.uses(allocationsApi, "connects to")
47 | }
48 |
49 | override fun defineViews(views: ViewSet) {
50 | views.createSystemContextView(system, "allocations-context", "Context overview of probation allocation tool").apply {
51 | addDefaultElements()
52 | removeRelationshipsNotConnectedToElement(system)
53 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ProbationCaseSampler.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.SoftwareSystem
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class ProbationCaseSampler private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var system: SoftwareSystem
12 |
13 | override fun defineModelEntities(model: Model) {
14 | system = model.addSoftwareSystem(
15 | "Probation Case Sampler",
16 | "API which produces a representative and evenly distributed list of probation cases " +
17 | "within a region and date range which form the basis of an on-site inspection"
18 | ).apply {
19 | Tags.DOMAIN_API.addTo(this)
20 | Tags.AREA_PROBATION.addTo(this)
21 | setUrl("https://dsdmoj.atlassian.net/wiki/spaces/NDSS/pages/1989181486/HMIP+Case+Sampling")
22 | }
23 | }
24 |
25 | override fun defineRelationships() {
26 | system.uses(Delius.system, "gets list of probation cases from")
27 | }
28 |
29 | override fun defineViews(views: ViewSet) {
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ProbationPractitioners.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Model
4 | import com.structurizr.model.Person
5 | import com.structurizr.view.ViewSet
6 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
7 | import uk.gov.justice.hmpps.architecture.annotations.Tags
8 |
9 | class ProbationPractitioners private constructor() {
10 | companion object : HMPPSSoftwareSystem {
11 | lateinit var nps: Person
12 | lateinit var crc: Person
13 | lateinit var spo: Person
14 |
15 | override fun defineModelEntities(model: Model) {
16 | nps = model.addPerson(
17 | "NPS probation practitioner",
18 | "National Probation Service employed probation officers in custody, court and the community"
19 | )
20 | spo = model.addPerson(
21 | "NPS senior probation officer",
22 | "National Probation Service employed senior probation officers"
23 | )
24 | crc = model.addPerson(
25 | "CRC offender manager",
26 | "Probation officers in custody, court and the community employed by intervention providers"
27 | ).apply { Tags.PROVIDER.addTo(this) }
28 | }
29 |
30 | override fun defineRelationships() {
31 | }
32 |
33 | override fun defineViews(views: ViewSet) {
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/ProbationTeamsService.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class ProbationTeamsService private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var api: Container
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem(
18 | "Probation team contact and reference service",
19 | "Exposes probation areas and Local Delivery Unit 'functional' mailboxes as an API"
20 | ).apply {
21 | Tags.DOMAIN_API.addTo(this)
22 | Tags.AREA_PROBATION.addTo(this)
23 | }
24 |
25 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
26 | url = "https://github.com/ministryofjustice/probation-teams"
27 | }
28 |
29 | val db = system.addContainer("Database", "Storage for probation areas and Local Delivery Unit 'functional' mailboxes", "PostgreSQL").apply {
30 | Tags.DATABASE.addTo(this)
31 | }
32 |
33 | api.uses(db, "connects to")
34 | }
35 |
36 | override fun defineRelationships() {
37 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
38 | }
39 |
40 | override fun defineViews(views: ViewSet) {
41 | views.createContainerView(system, "probation-teams-container", null).apply {
42 | addDefaultElements()
43 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/Reporting.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.ProblemArea
11 | import uk.gov.justice.hmpps.architecture.annotations.Tags
12 |
13 | class Reporting private constructor() {
14 | companion object : HMPPSSoftwareSystem {
15 | lateinit var ndmis: SoftwareSystem
16 | lateinit var newInterventionsETL: Container
17 | lateinit var landingBucket: Container
18 |
19 | lateinit var dataInnovationTeam: Person
20 | lateinit var nationalApplicationReportingTeam: Person
21 |
22 | lateinit var npsPerformanceOfficer: Person
23 | lateinit var prisonPerformanceTeam: Person
24 | lateinit var communityPerformanceTeam: Person
25 | lateinit var crcPerformanceAnalyst: Person
26 |
27 | override fun defineModelEntities(model: Model) {
28 | ndmis = model.addSoftwareSystem(
29 | "NDMIS",
30 | "(National Delius Management Information System) Provides reporting on nDelius data"
31 | ).apply {
32 | ProblemArea.GETTING_THE_RIGHT_REHABILITATION.addTo(this)
33 | }
34 |
35 | val database = ndmis.addContainer(
36 | "Probation reporting database",
37 | "Contains probation data transformed for reporting needs",
38 | "Oracle"
39 | ).apply {
40 | Tags.DATABASE.addTo(this)
41 | }
42 |
43 | landingBucket = ndmis.addContainer(
44 | "NDMIS ETL reporting landing bucket",
45 | "Collects daily snapshots of data from new services for hand-off to NDMIS (reporting)",
46 | "S3 bucket"
47 | ).apply {
48 | Tags.DATABASE.addTo(this)
49 | CloudPlatform.s3.add(this)
50 | }
51 |
52 | newInterventionsETL = ndmis.addContainer(
53 | "Interventions ETL job",
54 | "Storage area where data ingestion for business reporting starts for new probation services",
55 | "SAP Business Objects Data Services (BODS)"
56 | ).apply {
57 | Tags.PLANNED.addTo(this)
58 | uses(landingBucket, "extracts and transforms data from")
59 | uses(database, "stores copied and transformed data in")
60 | }
61 |
62 | communityPerformanceTeam = model.addPerson("Community Performance team", "Reporting on HMPPS performance in the community")
63 | crcPerformanceAnalyst = model.addPerson("CRC performance analyst").apply { Tags.PROVIDER.addTo(this) }
64 | dataInnovationTeam = model.addPerson("Data Innovation, Analysis and Linking team", "Works on linked data from various non-HMPPS government departments")
65 | nationalApplicationReportingTeam = model.addPerson("NART", "(National Applications Reporting Team) Responsible for the delivery of reporting to stakeholders")
66 | npsPerformanceOfficer = model.addPerson("NPS performance and quality officer")
67 | prisonPerformanceTeam = model.addPerson("Prison Performance team", "Reporting on HMPPS performance in prison")
68 |
69 | listOf(communityPerformanceTeam, prisonPerformanceTeam, dataInnovationTeam)
70 | .forEach { it.addProperty("org", "DASD") }
71 | }
72 |
73 | override fun defineRelationships() {
74 | ndmis.uses(Delius.database, "extracts and transforms data from", "Change Data Capture")
75 | communityPerformanceTeam.uses(ndmis, "uses reports in")
76 | crcPerformanceAnalyst.uses(ndmis, "uses reports in")
77 | dataInnovationTeam.uses(ndmis, "uses data from")
78 | nationalApplicationReportingTeam.uses(ndmis, "creates reports in")
79 | npsPerformanceOfficer.uses(ndmis, "uses reports in")
80 | prisonPerformanceTeam.uses(ndmis, "to provide details of offenders released into the community, looks into")
81 | }
82 |
83 | override fun defineViews(views: ViewSet) {
84 | views.createSystemContextView(ndmis, "ndmis-context", null).apply {
85 | addDefaultElements()
86 | enableAutomaticLayout(AutomaticLayout.RankDirection.LeftRight, 200, 200)
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/RestrictedPatientsApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class RestrictedPatientsApi private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Restricted Patients API",
18 | "Provides and creates information about restricted patiente"
19 | ).apply {
20 | Tags.DOMAIN_API.addTo(this)
21 | Tags.AREA_PRISONS.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/hmpps-restricted-patients-api"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for restricted patient information", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | api.uses(NOMIS.prisonApi, "retrieves and creates restricted patient information in")
38 | api.uses(NOMIS.offenderSearch, "retrieves prisoner data from")
39 | }
40 |
41 | override fun defineViews(views: ViewSet) {
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/StaffLookupApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Capability
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class StaffLookupApi private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var api: Container
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem(
18 | "Staff Lookup Service",
19 | "stores a copy of e-mail data and information like their first and last name associated with MoJ Staff who have email ending in @justice.gov.uk"
20 | ).apply {
21 | Capability.IDENTITY.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/hmpps-staff-lookup-service"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for staff copy", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | }
38 |
39 | override fun defineViews(views: ViewSet) {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/TierService.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class TierService private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var tierDb: Container
15 | lateinit var tierService: Container
16 | lateinit var tierToDeliusUpdate: Container
17 | lateinit var tierSqsTool: Container
18 |
19 | override fun defineModelEntities(model: Model) {
20 | system = model.addSoftwareSystem(
21 | "Tier Service",
22 | "Calculates and stores unified tier scores, representing the risk and needs of people under supervision"
23 | )
24 |
25 | tierDb = system.addContainer(
26 | "Tier Database",
27 | "Holds tier",
28 | "PostgreSQL"
29 | ).apply {
30 | Tags.DATABASE.addTo(this)
31 | CloudPlatform.rds.add(this)
32 | }
33 |
34 | tierService = system.addContainer(
35 | "Tier Service",
36 | "Tier micro-service containing business logic, persistence logic and REST API",
37 | "Kotlin + Spring Boot"
38 | ).apply {
39 | Tags.DOMAIN_API.addTo(this)
40 | Tags.AREA_PROBATION.addTo(this)
41 | CloudPlatform.kubernetes.add(this)
42 | uses(tierDb, "connects to", "JDBC")
43 | setUrl("https://github.com/ministryofjustice/hmpps-tier")
44 | }
45 |
46 | tierToDeliusUpdate = system.addContainer(
47 | "Tier to Delius Update",
48 | "Synchronises tier back into nDelius",
49 | "Kotlin + Spring Boot"
50 | ).apply {
51 | CloudPlatform.kubernetes.add(this)
52 | setUrl("https://github.com/ministryofjustice/hmpps-tier-to-delius-update")
53 | }
54 |
55 | tierSqsTool = system.addContainer(
56 | "Tier SQS Tool",
57 | "Tool to manage SQS messaging infrastructure such as automated retries",
58 | "Kotlin + Spring Boot"
59 | ).apply {
60 | CloudPlatform.kubernetes.add(this)
61 | setUrl("https://github.com/ministryofjustice/hmpps-tier-sqs-tool")
62 | }
63 | }
64 |
65 | override fun defineRelationships() {
66 | tierService.uses(Delius.probationOffenderEvents, "to know when to recalculate a tier, consumes management tier updated events from", "SNS+SQS")
67 | tierService.uses(Delius.communityApi, "retrieves data required for tier calculation from", "REST")
68 | tierService.uses(OASys.assessmentsApi, "retrieves data required for tier calculation from", "REST")
69 | tierService.uses(HMPPSDomainEvents.topic, "publishes tier update events to", "SNS")
70 |
71 | tierToDeliusUpdate.uses(HMPPSDomainEvents.topic, "consumes tier updates events from", "SNS+SQS")
72 | tierToDeliusUpdate.uses(Delius.communityApi, "updates tier in Delius with", "REST")
73 | }
74 |
75 | override fun defineViews(views: ViewSet) {
76 |
77 | views.createSystemContextView(system, "tier-service-context", null).apply {
78 | addDefaultElements()
79 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
80 | }
81 |
82 | views.createContainerView(system, "tier-service-container", null).apply {
83 | addDefaultElements()
84 | addAllContainersAndInfluencers()
85 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/TokenVerificationApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Capability
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class TokenVerificationApi private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var api: Container
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem(
18 | "Token verification API",
19 | "Verifies API tokens issues by HMPPS Auth to ensure they haven't expired or been revoked"
20 | ).apply {
21 | Capability.IDENTITY.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/token-verification-api"
26 | }
27 |
28 | val redis = system.addContainer("REDIS", "Tokens storage", "REDIS").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(redis, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | }
37 |
38 | override fun defineViews(views: ViewSet) {
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/UnpaidWorkService.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class UnpaidWorkService private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var riskAssessmentUi: Container
15 | lateinit var assessmentService: Container
16 | lateinit var storage: Container
17 | lateinit var gotenburg: Container
18 | lateinit var collector: Container
19 |
20 | override fun defineModelEntities(model: Model) {
21 | system = model.addSoftwareSystem(
22 | "Unpaid Work Assessment Service",
23 | "Digital Service for assessing a PoP's needs ahead of unpaid work (ie community payback) sessions"
24 | )
25 |
26 | val assessmentDb = system.addContainer(
27 | "Assessments Database",
28 | "Holds unpaid work assessment questionnaire and assessment data",
29 | "PostgreSQL"
30 | ).apply {
31 | Tags.DATABASE.addTo(this)
32 | CloudPlatform.rds.add(this)
33 | }
34 |
35 | gotenburg = system.addContainer(
36 | "Gotenburg PDF Generator",
37 | "Generates PDFs from data",
38 | "Java"
39 | ).apply {
40 | CloudPlatform.kubernetes.add(this)
41 | }
42 |
43 | storage = system.addContainer(
44 | "Assessments storage",
45 | "Storage for PDFs of completed assessments",
46 | "S3"
47 | ).apply {
48 | Tags.DATABASE.addTo(this)
49 | CloudPlatform.s3.add(this)
50 | }
51 |
52 | assessmentService = system.addContainer(
53 | "Assessments API",
54 | "Assessments business logic, providing REST API consumed by Risk Assessment UI web application, Authoritative source for unpaid work assessment data",
55 | "Kotlin + Spring Boot"
56 | ).apply {
57 | Tags.DOMAIN_API.addTo(this)
58 | Tags.AREA_PROBATION.addTo(this)
59 | uses(assessmentDb, "connects to", "JDBC")
60 | uses(gotenburg, "Creates assessments in PDF form", "REST")
61 | uses(storage, "Stores assessments in PDF form", "AWS API (REST)")
62 | url = "https://github.com/ministryofjustice/hmpps-assessments-api"
63 | CloudPlatform.kubernetes.add(this)
64 | }
65 |
66 | riskAssessmentUi = system.addContainer(
67 | "Assessments UI",
68 | "Web application, presenting risk assessment questionnaires",
69 | "Node + Express"
70 | ).apply {
71 | uses(assessmentService, "Display assessment questions and save answers using ")
72 | url = "https://github.com/ministryofjustice/hmpps-risk-assessment-ui"
73 | Tags.WEB_BROWSER.addTo(this)
74 | CloudPlatform.kubernetes.add(this)
75 | }
76 | }
77 |
78 | override fun defineRelationships() {
79 | listOf(riskAssessmentUi, assessmentService)
80 | .forEach { it.uses(HMPPSAuth.system, "authenticates via") }
81 |
82 | assessmentService.uses(AssessRisksAndNeeds.riskNeedsService, "Gets risk information from")
83 | assessmentService.uses(Delius.UPWIntegrationService, "Gets offender and offence details from")
84 | assessmentService.uses(PrepareCaseForSentence.courtCaseService, "Gets offender and offence details from")
85 | assessmentService.uses(OASys.assessmentsApi, "get offender past assessment details from")
86 | assessmentService.uses(HMPPSDomainEvents.topic, "fires events when new UPW assessment is complete")
87 | riskAssessmentUi.uses(HMPPSAudit.system, "records user interactions", "HTTPS")
88 | ProbationPractitioners.nps.uses(riskAssessmentUi, "records offender risks and needs")
89 | }
90 |
91 | override fun defineViews(views: ViewSet) {
92 | views.createSystemContextView(
93 | system,
94 | "unpaid-work-service-context", null
95 | ).apply {
96 | addDefaultElements()
97 | removeRelationshipsNotConnectedToElement(system)
98 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
99 | }
100 |
101 | views.createDeploymentView(system, "unpaid-work-service-deployment", "Deployment overview of the assess risks and needs services").apply {
102 | add(AWS.london)
103 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
104 | }
105 |
106 | views.createContainerView(system, "unpaid-work-service-container", null).apply {
107 | addDefaultElements()
108 | addAllContainersAndInfluencers()
109 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/UseOfForce.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.Person
6 | import com.structurizr.model.SoftwareSystem
7 | import com.structurizr.view.AutomaticLayout
8 | import com.structurizr.view.ViewSet
9 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
10 | import uk.gov.justice.hmpps.architecture.annotations.Notifier
11 | import uk.gov.justice.hmpps.architecture.annotations.Tags
12 |
13 | class UseOfForce private constructor() {
14 |
15 | companion object : HMPPSSoftwareSystem {
16 | lateinit var model: Model
17 | lateinit var system: SoftwareSystem
18 | lateinit var useOfForceService: Container
19 | lateinit var db: Container
20 | lateinit var redis: Container
21 |
22 | lateinit var reportCreator: Person
23 | lateinit var coordinator: Person
24 | lateinit var reviewer: Person
25 | lateinit var involvedStaff: Person
26 |
27 | override fun defineModelEntities(model: Model) {
28 | this.model = model
29 |
30 | system = model.addSoftwareSystem(
31 | "Use of Force",
32 | "A system for allowing prison staff to report a Use of Force instance",
33 | ).apply {
34 | val PRISON_PROBATION_PROPERTY_NAME = "business_unit"
35 | val PRISON_SERVICE = "prisons"
36 | addProperty(PRISON_PROBATION_PROPERTY_NAME, PRISON_SERVICE)
37 | }
38 |
39 | db = system.addContainer(
40 | "Use of Force Database",
41 | "Database to store Use of Force reports", "RDS Postgres DB"
42 | ).apply {
43 | Tags.DATABASE.addTo(this)
44 | CloudPlatform.rds.add(this)
45 | }
46 |
47 | redis = system.addContainer(
48 | "In-memory data store",
49 | "handles processing queues for email distribution and NOMIS movements",
50 | "REDIS"
51 | ).apply {
52 | Tags.SOFTWARE_AS_A_SERVICE.addTo(this)
53 | CloudPlatform.elasticache.add(this)
54 | }
55 |
56 | useOfForceService = system.addContainer("Use of Force service", "Allows prison staff to report a Use of Force instance", "Node").apply {
57 | setUrl("https://github.com/ministryofjustice/use-of-force")
58 |
59 | CloudPlatform.kubernetes.add(this)
60 | }
61 |
62 | reportCreator = model.addPerson("Report creator user", "Prison staff who creates reports and provides statements").apply {
63 | uses(useOfForceService, "Creates Use of Force reports")
64 | }
65 |
66 | coordinator = model.addPerson("Coordinator user", "Prison staff who can view all complete and in progress reports and statements across their caseload").apply {
67 | uses(useOfForceService, "Corrects recorded involved staff if needed and deletes unnecessary duplicates")
68 | }
69 |
70 | reviewer = model.addPerson("Reviewer user", "Prison staff who can review and maintain reports across their caseload").apply {
71 | uses(useOfForceService, "Reviews Use of Force reports to ensure statements are submitted in a timely manner")
72 | }
73 |
74 | involvedStaff = model.addPerson("Involved staff user", "Prison staff who provides statements").apply {
75 | uses(useOfForceService, "Submits Use of Force statements")
76 | }
77 | }
78 |
79 | override fun defineRelationships() {
80 | useOfForceService.uses(HMPPSAuth.system, "HTTPS Rest API")
81 | useOfForceService.uses(NOMIS.prisonApi, "extract NOMIS offender data")
82 | useOfForceService.uses(NOMIS.offenderSearch, "to search for prisoners")
83 | useOfForceService.uses(TokenVerificationApi.api, "validates user tokens via", "HTTPS Rest API")
84 |
85 | Notifier.delivers(
86 | useOfForceService,
87 | listOf(
88 | Triple(
89 | listOf(involvedStaff),
90 | "Emails notification that staff member has been added to reports and incomplete statement reminders",
91 | "email"
92 | ),
93 | )
94 | )
95 | }
96 |
97 | override fun defineViews(views: ViewSet) {
98 | views.createSystemContextView(
99 | system, "useOfForceSystemContext", "The system context diagram for the Use of Force service"
100 | ).apply {
101 | addDefaultElements()
102 |
103 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
104 | }
105 |
106 | views.createSystemContextView(
107 | system, "useOfForceUserRelationships", "Relationships between users and the Use of Force service"
108 | ).apply {
109 | add(system)
110 | add(reportCreator)
111 | add(coordinator)
112 | add(reviewer)
113 | add(involvedStaff)
114 |
115 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
116 | }
117 |
118 | views.createContainerView(system, "useOfForceContainer", "Use of Force service container view").apply {
119 | addDefaultElements()
120 | remove(coordinator)
121 | remove(reviewer)
122 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
123 | }
124 |
125 | views.createDeploymentView(system, "useOfForceContainerProductionDeployment", "The Production deployment scenario for the Use of Force service").apply {
126 | add(AWS.london)
127 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 300, 300)
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/UserPreferenceApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Capability
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class UserPreferenceApi private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var api: Container
15 |
16 | override fun defineModelEntities(model: Model) {
17 | system = model.addSoftwareSystem(
18 | "User Preference API",
19 | "Stores preference information associated with a HMPPS Auth User"
20 | ).apply {
21 | Capability.IDENTITY.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/hmpps-user-preferences"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for user preferences", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | }
38 |
39 | override fun defineViews(views: ViewSet) {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/WMT.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.AutomaticLayout
7 | import com.structurizr.view.ViewSet
8 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
9 | import uk.gov.justice.hmpps.architecture.annotations.Tags
10 |
11 | class WMT private constructor() {
12 | companion object : HMPPSSoftwareSystem {
13 | lateinit var system: SoftwareSystem
14 | lateinit var ui: Container
15 | lateinit var batchProcessor: Container
16 | lateinit var workloadApi: Container
17 | lateinit var db: Container
18 |
19 | override fun defineModelEntities(model: Model) {
20 | system = model.addSoftwareSystem(
21 | "Workload Measurement Tool",
22 | "(WMT) Helps probation practitioners schedule their time based on service user risk"
23 | )
24 |
25 | ui = system.addContainer("WMT Web", "View workload data", "Node").apply {
26 | url = "https://github.com/ministryofjustice/wmt-web"
27 | }
28 |
29 | batchProcessor = system.addContainer("WMT Worker", "Overnight processing of NART report extract", "Node").apply {
30 | url = "https://github.com/ministryofjustice/wmt-worker"
31 | }
32 | workloadApi = system.addContainer("Workload API", "Provides information related to workload data", "Kotlin").apply {
33 | url = "https://github.com/ministryofjustice/hmpps-workload"
34 | }
35 |
36 | db = system.addContainer("Database", "Storage for workload data", "PostgreSQL").apply {
37 | Tags.DATABASE.addTo(this)
38 | }
39 | }
40 |
41 | override fun defineRelationships() {
42 | ProbationPractitioners.nps.uses(system, "finds out their community case load by looking at")
43 | system.uses(Reporting.ndmis, "draws offender risk and allocation data from")
44 |
45 | workloadApi.uses(HMPPSDomainEvents.topic, "Publishes allocation event messages")
46 |
47 | ui.uses(db, "connects to")
48 | batchProcessor.uses(db, "connects to")
49 | workloadApi.uses(db, "connects to")
50 | }
51 |
52 | override fun defineViews(views: ViewSet) {
53 | views.createSystemContextView(system, "wmt-context", "Context overview of WMT").apply {
54 | addDefaultElements()
55 | removeRelationshipsNotConnectedToElement(system)
56 | enableAutomaticLayout(AutomaticLayout.RankDirection.TopBottom, 500, 500)
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/model/WhereaboutsApi.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.model
2 |
3 | import com.structurizr.model.Container
4 | import com.structurizr.model.Model
5 | import com.structurizr.model.SoftwareSystem
6 | import com.structurizr.view.ViewSet
7 | import uk.gov.justice.hmpps.architecture.HMPPSSoftwareSystem
8 | import uk.gov.justice.hmpps.architecture.annotations.Tags
9 |
10 | class WhereaboutsApi private constructor() {
11 | companion object : HMPPSSoftwareSystem {
12 | lateinit var system: SoftwareSystem
13 | lateinit var api: Container
14 |
15 | override fun defineModelEntities(model: Model) {
16 | system = model.addSoftwareSystem(
17 | "Whereabouts API",
18 | "Provides and creates appointment information about prisoners"
19 | ).apply {
20 | Tags.DOMAIN_API.addTo(this)
21 | Tags.AREA_PRISONS.addTo(this)
22 | }
23 |
24 | api = system.addContainer("API", "API", "Kotlin + Spring Boot").apply {
25 | url = "https://github.com/ministryofjustice/whereabouts-api"
26 | }
27 |
28 | val db = system.addContainer("Database", "Storage for appointment information, court bookings", "PostgreSQL").apply {
29 | Tags.DATABASE.addTo(this)
30 | }
31 |
32 | api.uses(db, "connects to")
33 | }
34 |
35 | override fun defineRelationships() {
36 | api.uses(HMPPSAuth.app, "validates API tokens via", "JWK")
37 | api.uses(NOMIS.prisonApi, "retrieves and create appointments in")
38 | }
39 |
40 | override fun defineViews(views: ViewSet) {
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/views/HMPPSDataAPI.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.views
2 |
3 | import com.structurizr.view.ViewSet
4 | import uk.gov.justice.hmpps.architecture.model.AssessRisksAndNeeds
5 | import uk.gov.justice.hmpps.architecture.model.Delius
6 | import uk.gov.justice.hmpps.architecture.model.HMPPSAPI
7 | import uk.gov.justice.hmpps.architecture.model.NOMIS
8 | import uk.gov.justice.hmpps.architecture.model.OASys
9 |
10 | class HMPPSDataAPI private constructor() {
11 |
12 | companion object : HMPPSView {
13 |
14 | override fun defineViews(views: ViewSet) {
15 | views.createContainerView(
16 | HMPPSAPI.system,
17 | "HMPPSAPIProbationDataView",
18 | "HMPPS Probation Data API services"
19 | ).apply {
20 | model.softwareSystems.filter {
21 | it.hasTag("AREA_PROBATION") && it.hasTag("DATA_API")
22 | }.map { add(it) }
23 |
24 | model.softwareSystems.forEach {
25 | it.containers.filter {
26 | it.hasTag("AREA_PROBATION") && it.hasTag("DATA_API")
27 | }.map { add(it) }
28 | }
29 |
30 | // API data sources
31 | add(Delius.database)
32 | add(Delius.offenderElasticsearchStore)
33 | add(OASys.oasysDB)
34 | }
35 |
36 | views.createContainerView(
37 | HMPPSAPI.system,
38 | "HMPPSAPIPrisonsDataView",
39 | "HMPPS Prisons Data API services"
40 | ).apply {
41 | model.softwareSystems.filter {
42 | it.hasTag("AREA_PRISONS") && it.hasTag("DATA_API")
43 | }.map { add(it) }
44 |
45 | model.softwareSystems.forEach {
46 | it.containers.filter {
47 | it.hasTag("AREA_PRISONS") && it.hasTag("DATA_API")
48 | }.map { add(it) }
49 | }
50 |
51 | // API data sources
52 | add(NOMIS.db)
53 | add(NOMIS.offenderSearch)
54 | add(NOMIS.elasticSearchStore)
55 | }
56 |
57 | views.createContainerView(
58 | HMPPSAPI.system,
59 | "HMPPSAPIDeliusDataView",
60 | "HMPPS Delius Data API services"
61 | ).apply {
62 |
63 | // API services
64 | add(Delius.communityApi)
65 | add(Delius.deliusApi)
66 | add(Delius.offenderSearch)
67 |
68 | // API data sources
69 | add(Delius.database)
70 | add(Delius.offenderElasticsearchStore)
71 | }
72 |
73 | views.createContainerView(
74 | HMPPSAPI.system,
75 | "HMPPSAPIOASysDataView",
76 | "HMPPS OASys Data API services"
77 | ).apply {
78 |
79 | // API services
80 | add(OASys.assessmentsApi)
81 | add(OASys.ORDSApi)
82 | add(AssessRisksAndNeeds.riskNeedsService)
83 |
84 | // API data sources
85 | add(OASys.oasysDB)
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/views/HMPPSDomainAPI.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.views
2 |
3 | import com.structurizr.view.ViewSet
4 | import uk.gov.justice.hmpps.architecture.annotations.Capability
5 | import uk.gov.justice.hmpps.architecture.model.Delius
6 | import uk.gov.justice.hmpps.architecture.model.HMPPSAPI
7 | import uk.gov.justice.hmpps.architecture.model.HMPPSAuth
8 | import uk.gov.justice.hmpps.architecture.model.NOMIS
9 |
10 | class HMPPSDomainAPI private constructor() {
11 | companion object : HMPPSView {
12 | override fun defineViews(views: ViewSet) {
13 |
14 | views.createContainerView(
15 | HMPPSAPI.system,
16 | "HMPPSAPIProbationDomainView",
17 | "HMPPS Internal Probation Domain API services"
18 | ).apply {
19 | model.softwareSystems.filter { it.hasTag("AREA_PROBATION") }.map { add(it) }
20 |
21 | model.softwareSystems.forEach {
22 | it.containers.filter { it.hasTag("AREA_PROBATION") }.map { add(it) }
23 | }
24 | }
25 |
26 | views.createContainerView(
27 | HMPPSAPI.system,
28 | "HMPPSAPIPrisonsDomainView",
29 | "HMPPS Internal Prisons Domain API services"
30 | ).apply {
31 | model.softwareSystems.filter { it.hasTag("AREA_PRISONS") }.map { add(it) }
32 |
33 | model.softwareSystems.forEach {
34 | it.containers.filter { it.hasTag("AREA_PRISONS") }.map { add(it) }
35 | }
36 | }
37 |
38 | views.createContainerView(
39 | HMPPSAPI.system,
40 | "HMPPSAPIAuthView",
41 | "HMPPS Internal API Authentication services"
42 | ).apply {
43 | model.softwareSystems.filter { Capability.IDENTITY.isOn(it) }.map { add(it) }
44 | model.softwareSystems.forEach {
45 | it.containers.filter { Capability.IDENTITY.isOn(it) }.map { add(it) }
46 | }
47 |
48 | add(Delius.communityApi)
49 | add(Delius.database)
50 | add(NOMIS.prisonApi)
51 | add(NOMIS.db)
52 | add(HMPPSAuth.database)
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/views/HMPPSInternalAPI.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.views
2 |
3 | import com.structurizr.view.ViewSet
4 | import uk.gov.justice.hmpps.architecture.model.Delius
5 | import uk.gov.justice.hmpps.architecture.model.HMPPSAPI
6 | import uk.gov.justice.hmpps.architecture.model.NOMIS
7 | import uk.gov.justice.hmpps.architecture.model.OASys
8 |
9 | class HMPPSInternalAPI private constructor() {
10 |
11 | companion object : HMPPSView {
12 |
13 | override fun defineViews(views: ViewSet) {
14 |
15 | views.createContainerView(
16 | HMPPSAPI.system,
17 | "HMPPSAPIView",
18 | "HMPPS API services"
19 | ).apply {
20 | model.softwareSystems.filter {
21 | it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")
22 | }.map { add(it) }
23 |
24 | model.softwareSystems.forEach {
25 | it.containers.filter {
26 | it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")
27 | }.map { add(it) }
28 | }
29 |
30 | // API data sources
31 | add(NOMIS.db)
32 | add(NOMIS.elasticSearchStore)
33 | add(Delius.database)
34 | add(Delius.offenderElasticsearchStore)
35 | add(OASys.oasysDB)
36 | }
37 |
38 | views.createContainerView(
39 | HMPPSAPI.system,
40 | "HMPPSAPIProbationAreaView",
41 | "HMPPS API Probation Area Services "
42 | ).apply {
43 | model.softwareSystems.filter {
44 | (it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")) && it.hasTag("AREA_PROBATION")
45 | }.map { add(it) }
46 |
47 | model.softwareSystems.forEach {
48 | it.containers.filter {
49 | (it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")) && it.hasTag("AREA_PROBATION")
50 | }.map { add(it) }
51 | }
52 |
53 | // API data sources
54 | add(Delius.database)
55 | add(Delius.offenderElasticsearchStore)
56 | add(OASys.oasysDB)
57 | }
58 |
59 | views.createContainerView(
60 | HMPPSAPI.system,
61 | "HMPPSAPIPrisonsAreaView",
62 | "HMPPS API Prisons Area Services "
63 | ).apply {
64 | model.softwareSystems.filter {
65 | (it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")) && it.hasTag("AREA_PRISONS")
66 | }.map { add(it) }
67 |
68 | model.softwareSystems.forEach {
69 | it.containers.filter {
70 | (it.hasTag("DOMAIN_API") || it.hasTag("DATA_API")) && it.hasTag("AREA_PRISONS")
71 | }.map { add(it) }
72 | }
73 |
74 | // API data sources
75 | add(NOMIS.db)
76 | add(NOMIS.elasticSearchStore)
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/kotlin/uk/gov/justice/hmpps/architecture/views/HMPPSView.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.views
2 |
3 | import com.structurizr.view.ViewSet
4 |
5 | interface HMPPSView {
6 | fun defineViews(views: ViewSet)
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/kotlin/uk/gov/justice/hmpps/architecture/export/backstage/BackstageExporterTest.kt:
--------------------------------------------------------------------------------
1 | package uk.gov.justice.hmpps.architecture.export.backstage
2 |
3 | import com.structurizr.util.WorkspaceUtils
4 | import org.assertj.core.api.Assertions.assertThat
5 | import org.junit.jupiter.api.Test
6 |
7 | class BackstageExporterTest {
8 |
9 | val backstageExporter = BackstageExporter()
10 |
11 | @Test
12 | fun `Converts workspace to backstage yaml`() {
13 | val workspace = WorkspaceUtils.fromJson("/structurizr.json".readResourceAsText())
14 | val backstageExport = backstageExporter.export(workspace)
15 |
16 | assertThat(backstageExport).isNotEqualToIgnoringWhitespace("/backstage.yaml".readResourceAsText())
17 | }
18 |
19 | fun String.readResourceAsText(): String {
20 | return BackstageExporterTest::class.java.getResource(this).readText()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/resources/backstage.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: "backstage.io/v1alpha1"
3 | kind: "System"
4 | metadata:
5 | name: "hmpps-auth"
6 | title: "HMPPS Auth"
7 | description: "Allows users to login into digital services"
8 | spec:
9 | owner: "hmpps-undefined"
10 | ---
11 | apiVersion: "backstage.io/v1alpha1"
12 | kind: "Component"
13 | metadata:
14 | name: "hmpps-auth"
15 | title: "HMPPS Auth"
16 | description: "UI and OAuth2 server"
17 | links:
18 | - url: "https://github.com/ministryofjustice/hmpps-auth"
19 | title: "GitHub Repo"
20 | icon: "github"
21 | - url: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth/swagger-ui.html"
22 | title: "API Docs"
23 | annotations:
24 | backstage.io/source-location: "url:https://github.com/ministryofjustice/hmpps-auth"
25 | spec:
26 | type: "service"
27 | lifecycle: "production"
28 | system: "hmpps-auth"
29 | owner: "hmpps-undefined"
30 | dependsOn:
31 | - "Component:internal-auth-database"
32 | providesApis:
33 | - "hmpps-auth"
34 | consumesApis:
35 | - "internal-auth-database"
36 | ---
37 | apiVersion: "backstage.io/v1alpha1"
38 | kind: "API"
39 | metadata:
40 | name: "hmpps-auth"
41 | description: "API provided by hmpps-auth"
42 | spec:
43 | type: "openapi"
44 | lifecycle: "production"
45 | system: "hmpps-auth"
46 | owner: "hmpps-undefined"
47 | definition:
48 | $text: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth/v3/api-docs"
49 | ---
50 | apiVersion: "backstage.io/v1alpha1"
51 | kind: "Component"
52 | metadata:
53 | name: "internal-auth-database"
54 | title: "Internal Auth Database"
55 | description: "Holds explicit credentials, roles, multi-factor settings and banning\
56 | \ data"
57 | spec:
58 | type: "database"
59 | lifecycle: "production"
60 | system: "hmpps-auth"
61 | owner: "hmpps-undefined"
62 | ---
63 | apiVersion: "backstage.io/v1alpha1"
64 | kind: "System"
65 | metadata:
66 | name: "prison-staff-hub"
67 | title: "Prison Staff Hub"
68 | description: "The web app that contains the main features"
69 | spec:
70 | owner: "hmpps-undefined"
71 |
--------------------------------------------------------------------------------
/src/test/resources/structurizr.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 123,
3 | "name": "HM Prison and Probation Service",
4 | "description": "Systems related to the custody and probation of offenders",
5 | "configuration": {},
6 | "model": {
7 | "enterprise": {
8 | "name": "HM Prison and Probation Service"
9 | },
10 | "people": [
11 | {
12 | "id": "11",
13 | "tags": "Element,Person",
14 | "name": "DPS User",
15 | "description": "User that logs into DPS via HMPPS Auth",
16 | "relationships": [
17 | {
18 | "id": "21",
19 | "tags": "Relationship",
20 | "sourceId": "11",
21 | "destinationId": "31",
22 | "description": "Logs in to HMPPS Auth"
23 | }
24 | ]
25 | }
26 | ],
27 | "softwareSystems": [
28 | {
29 | "id": "31",
30 | "tags": "Element,Software System",
31 | "name": "HMPPS Auth",
32 | "description": "Allows users to login into digital services",
33 | "location": "Internal",
34 | "containers": [
35 | {
36 | "id": "51",
37 | "tags": "Element,Container,WEB_BROWSER",
38 | "url": "https://github.com/ministryofjustice/hmpps-auth",
39 | "properties": {
40 | "api-docs-url": "https://sign-in-dev.hmpps.service.justice.gov.uk/auth/swagger-ui.html"
41 | },
42 | "name": "HMPPS Auth",
43 | "description": "UI and OAuth2 server",
44 | "relationships": [
45 | {
46 | "id": "61",
47 | "tags": "Relationship",
48 | "sourceId": "51",
49 | "destinationId": "52",
50 | "description": "connects to"
51 | }
52 | ],
53 | "technology": "Spring Boot + Java"
54 | },
55 | {
56 | "id": "52",
57 | "tags": "Element,Container,DATABASE",
58 | "name": "Internal Auth Database",
59 | "description": "Holds explicit credentials, roles, multi-factor settings and banning data",
60 | "technology": "Microsoft SQL Server"
61 | }
62 | ]
63 | },
64 | {
65 | "id": "32",
66 | "tags": "Element,Software System",
67 | "name": "Prison Staff Hub",
68 | "description": "The web app that contains the main features",
69 | "location": "Internal",
70 | "relationships": [
71 | {
72 | "id": "71",
73 | "tags": "Relationship",
74 | "sourceId": "32",
75 | "destinationId": "31",
76 | "description": "authorises users and requests access tokens from",
77 | "technology": "OAuth2/JWT"
78 | }
79 | ]
80 | }
81 | ]
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------