├── .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 | ![Publish](https://github.com/ministryofjustice/hmpps-architecture-as-code/workflows/Publish/badge.svg) 2 | ![Build and validate](https://github.com/ministryofjustice/hmpps-architecture-as-code/workflows/Build%20and%20validate/badge.svg) 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 | [![Overview](https://static.structurizr.com/workspace/56937/diagrams/system-overview.png)](https://structurizr.com/share/56937/diagrams#system-overview) 29 | 30 | ![Overview key](https://static.structurizr.com/workspace/56937/diagrams/system-overview-key.png) 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 | ![Container diagram](https://static.structurizr.com/workspace/56937/diagrams/nomiscontainer.png) 42 | 43 | ![Container diagram legend](https://static.structurizr.com/workspace/56937/diagrams/nomiscontainer-key.png) 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 | --------------------------------------------------------------------------------