From 8368b700d871761b9a5ab794b119c6c125c0910c Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 22:02:22 +0100 Subject: [PATCH 01/17] define the contracts for the architecture decision record design Signed-off-by: Romain Rochegude --- .../com/tngtech/archunit/library/adr/Adr.java | 40 +++++++++++++++++++ .../archunit/library/adr/Metadata.java | 29 ++++++++++++++ .../library/adr/OptionProsAndCons.java | 21 ++++++++++ 3 files changed, 90 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java new file mode 100644 index 0000000000..fbd496c91e --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java @@ -0,0 +1,40 @@ +package com.tngtech.archunit.library.adr; + +import java.util.List; +import java.util.Optional; + +/** + * Represents an Architecture Decision Record (ADR) + * such as these templates. + */ +public interface Adr { + Optional metadata(); + + Adr withMetadata(final Metadata metadata); + + String title(); + + Optional> decisionDrivers(); + + Adr withDecisionDrivers(final List decisionDrivers); + + List consideredOptions(); + + String decisionOutcome(); + + Optional> consequences(); + + Adr withConsequences(final List consequences); + + Optional confirmation(); + + Adr withConfirmation(final String confirmation); + + Optional> optionProsAndCons(); + + Adr withOptionProsAndCons(final List optionProsAndCons); + + Optional moreInformation(); + + Adr withMoreInformation(final String moreInformation); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java new file mode 100644 index 0000000000..a42ffe1f49 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java @@ -0,0 +1,29 @@ +package com.tngtech.archunit.library.adr; + +import java.util.List; +import java.util.Optional; + +/** + * Metadata of the ADR. + */ +public interface Metadata { + Optional status(); + + Metadata withStatus(final String status); + + Optional date(); + + Metadata withDate(final String date); + + Optional> decisionMakers(); + + Metadata withDecisionMakers(final List decisionMakers); + + Optional> consulted(); + + Metadata withConsulted(final List consulted); + + Optional> informed(); + + Metadata withInformed(final List informed); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java new file mode 100644 index 0000000000..cacf9700ab --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java @@ -0,0 +1,21 @@ +package com.tngtech.archunit.library.adr; + +import java.util.List; +import java.util.Optional; + +/** + * Represents an option of an ADR with its pros and cons. + */ +public interface OptionProsAndCons { + String title(); + + Optional example(); + + OptionProsAndCons withExample(final String example); + + Optional description(); + + OptionProsAndCons withDescription(final String description); + + List prosAndCons(); +} From 512d9ba8a3006762c1d4cdd874037fda3353a692 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 22:17:22 +0100 Subject: [PATCH 02/17] add markdown specific implementations Signed-off-by: Romain Rochegude --- .../archunit/library/adr/markdown/MdAdr.java | 108 ++++++++++++++++++ .../library/adr/markdown/MdMetadata.java | 70 ++++++++++++ .../adr/markdown/MdOptionProsAndCons.java | 50 ++++++++ 3 files changed, 228 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java new file mode 100644 index 0000000000..ad13e8a601 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -0,0 +1,108 @@ +package com.tngtech.archunit.library.adr.markdown; + +import com.tngtech.archunit.library.adr.Adr; +import com.tngtech.archunit.library.adr.Metadata; +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +public final class MdAdr implements Adr { + + private Metadata metadata; + private final String title; + private List decisionDrivers; + private final List consideredOptions; + private final String decisionOutcome; + private List consequences; + private String confirmation; + private List optionProsAndCons; + private String moreInformation; + + public MdAdr(final String title, final List consideredOptions, final String decisionOutcome) { + this.title = title; + this.consideredOptions = consideredOptions; + this.decisionOutcome = decisionOutcome; + } + + @Override + public Optional metadata() { + return Optional.ofNullable(this.metadata); + } + + @Override + public Adr withMetadata(final Metadata metadata) { + this.metadata = metadata; + return this; + } + + @Override + public String title() { + return this.title; + } + + @Override + public Optional> decisionDrivers() { + return Optional.ofNullable(this.decisionDrivers); + } + + @Override + public Adr withDecisionDrivers(final List decisionDrivers) { + this.decisionDrivers = decisionDrivers; + return this; + } + + @Override + public List consideredOptions() { + return this.consideredOptions; + } + + @Override + public String decisionOutcome() { + return this.decisionOutcome; + } + + @Override + public Optional> consequences() { + return Optional.ofNullable(this.consequences); + } + + @Override + public Adr withConsequences(final List consequences) { + this.consequences = consequences; + return this; + } + + @Override + public Optional confirmation() { + return Optional.ofNullable(this.confirmation); + } + + @Override + public Adr withConfirmation(final String confirmation) { + this.confirmation = confirmation; + return this; + } + + @Override + public Optional> optionProsAndCons() { + return Optional.ofNullable(this.optionProsAndCons); + } + + @Override + public Adr withOptionProsAndCons(final List optionProsAndCons) { + this.optionProsAndCons = optionProsAndCons; + return this; + } + + @Override + public Optional moreInformation() { + return Optional.ofNullable(this.moreInformation); + } + + @Override + public Adr withMoreInformation(final String moreInformation) { + this.moreInformation = moreInformation; + return this; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java new file mode 100644 index 0000000000..5c0e272808 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java @@ -0,0 +1,70 @@ +package com.tngtech.archunit.library.adr.markdown; + +import com.tngtech.archunit.library.adr.Metadata; + +import java.util.List; +import java.util.Optional; + +public final class MdMetadata implements Metadata { + + private String status; + private String date; + private List decisionMakers; + private List consulted; + private List informed; + + @Override + public Optional status() { + return Optional.ofNullable(this.status); + } + + @Override + public Metadata withStatus(final String status) { + this.status = status; + return this; + } + + @Override + public Optional date() { + return Optional.ofNullable(this.date); + } + + @Override + public Metadata withDate(final String date) { + this.date = date; + return this; + } + + @Override + public Optional> decisionMakers() { + return Optional.ofNullable(this.decisionMakers); + } + + @Override + public Metadata withDecisionMakers(final List decisionMakers) { + this.decisionMakers = decisionMakers; + return this; + } + + @Override + public Optional> consulted() { + return Optional.ofNullable(this.consulted); + } + + @Override + public Metadata withConsulted(final List consulted) { + this.consulted = consulted; + return this; + } + + @Override + public Optional> informed() { + return Optional.ofNullable(this.informed); + } + + @Override + public Metadata withInformed(final List informed) { + this.informed = informed; + return this; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java new file mode 100644 index 0000000000..c70ea8ed39 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -0,0 +1,50 @@ +package com.tngtech.archunit.library.adr.markdown; + +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +public final class MdOptionProsAndCons implements OptionProsAndCons { + private final String title; + private String example; + private String description; + private final List prosAndCons; + + public MdOptionProsAndCons(final String title, final List prosAndCons) { + this.title = title; + this.prosAndCons = prosAndCons; + } + + @Override + public String title() { + return this.title; + } + + @Override + public Optional example() { + return Optional.ofNullable(this.example); + } + + @Override + public OptionProsAndCons withExample(final String example) { + this.example = example; + return this; + } + + @Override + public Optional description() { + return Optional.ofNullable(this.description); + } + + @Override + public OptionProsAndCons withDescription(final String description) { + this.description = description; + return this; + } + + @Override + public List prosAndCons() { + return this.prosAndCons; + } +} From 986cd54c9e214b7bfea4954cd08045ee6cc90065 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 22:45:46 +0100 Subject: [PATCH 03/17] add failing test Signed-off-by: Romain Rochegude --- .../library/adr/markdown/MdAdrTest.java | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java new file mode 100644 index 0000000000..303605bb15 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -0,0 +1,146 @@ +package com.tngtech.archunit.library.adr.markdown; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.core.IsEqual; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class MdAdrTest { + @Test + void testMdGeneration() { + MatcherAssert.assertThat( + new MdAdr( + "{short title, representative of solved problem and found solution}", + Arrays.asList( + "{title of option 1}", + "{title of option 2}", + "{title of option 3}" + ), + "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}." + ).withMetadata( + new MdMetadata().withStatus( + "accepted" + ).withDate( + "YYYY-MM-DD" + ).withDecisionMakers( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withConsulted( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withInformed( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ) + ).withDecisionDrivers( + Arrays.asList( + "{decision driver 1, e.g., a force, facing concern, ...}", + "{decision driver 2, e.g., a force, facing concern, ...}" + ) + ).withConsequences( + Arrays.asList( + "Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}", + "Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}" + ) + ).withConfirmation( + "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}" + ).withOptionProsAndCons( + Arrays.asList( + new MdOptionProsAndCons( + "{title of option 1}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}" + ) + ).withDescription("{description}").withExample("{example}"), + new MdOptionProsAndCons( + "{title of other option}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" + ) + ) + ) + ).withMoreInformation( + "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" + ).toString(), + new IsEqual<>( + " ---\n" + + " status: accepted\n" + + " date: YYYY-MM-DD\n" + + " decision-makers: John Doe, Romain Rochegude\n" + + " consulted: John Doe, Romain Rochegude\n" + + " informed: John Doe, Romain Rochegude\n" + + " ---\n" + + "\n" + + " # {short title, representative of solved problem and found solution}\n" + + "\n" + + " ## Context and Problem Statement\n" + + "\n" + + " {Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + + "\n" + + " ## Decision Drivers\n" + + "\n" + + " * {decision driver 1, e.g., a force, facing concern, ...}\n" + + " * {decision driver 2, e.g., a force, facing concern, ...}\n" + + "\n" + + " ## Considered Options\n" + + "\n" + + " * {title of option 1}\n" + + " * {title of option 2}\n" + + " * {title of option 3}\n" + + "\n" + + " ## Decision Outcome\n" + + "\n" + + " Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + + "\n" + + " ### Consequences\n" + + "\n" + + " * Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + + " * Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + + "\n" + + " ### Confirmation\n" + + "\n" + + " {Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + + "\n" + + " ## Pros and Cons of the Options\n" + + "\n" + + " ### {title of option 1}\n" + + "\n" + + " {description}\n" + + "\n" + + " {example}\n" + + "\n" + + " * Good, because {argument a}\n" + + " * Good, because {argument b}\n" + + " * Neutral, because {argument c}\n" + + " * Bad, because {argument d}\n" + + "\n" + + " ### {title of other option}\n" + + "\n" + + " {description}\n" + + "\n" + + " {example}\n" + + "\n" + + " * Good, because {argument a}\n" + + " * Good, because {argument b}\n" + + " * Neutral, because {argument c}\n" + + " * Bad, because {argument d}\n" + + "\n" + + " ## More Information\n" + + "\n" + + " {You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" + + ) + ); + } +} From cb8b73ce21ca80f62e27ae95cc0e894895a67cdc Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 22:46:33 +0100 Subject: [PATCH 04/17] code formatting Signed-off-by: Romain Rochegude --- .../com/tngtech/archunit/library/adr/Adr.java | 15 +++++++++++++++ .../tngtech/archunit/library/adr/Metadata.java | 15 +++++++++++++++ .../archunit/library/adr/OptionProsAndCons.java | 15 +++++++++++++++ .../archunit/library/adr/markdown/MdAdr.java | 15 +++++++++++++++ .../archunit/library/adr/markdown/MdMetadata.java | 15 +++++++++++++++ .../library/adr/markdown/MdOptionProsAndCons.java | 15 +++++++++++++++ 6 files changed, 90 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java index fbd496c91e..8fab62ce40 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr; import java.util.List; diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java index a42ffe1f49..541628e30a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr; import java.util.List; diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java index cacf9700ab..91f2109e5a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr; import java.util.List; diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java index ad13e8a601..acd7b59f87 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr.markdown; import com.tngtech.archunit.library.adr.Adr; diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java index 5c0e272808..092c3fece9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr.markdown; import com.tngtech.archunit.library.adr.Metadata; diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java index c70ea8ed39..290872c3a4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tngtech.archunit.library.adr.markdown; import com.tngtech.archunit.library.adr.OptionProsAndCons; From 856abf0366c6e833ec7d8e764a0509333d95301c Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 23:36:02 +0100 Subject: [PATCH 05/17] add failing test Signed-off-by: Romain Rochegude --- .../library/adr/markdown/MdAdrTest.java | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java index 303605bb15..fb82dc37e2 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -12,6 +12,7 @@ void testMdGeneration() { MatcherAssert.assertThat( new MdAdr( "{short title, representative of solved problem and found solution}", + "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}", Arrays.asList( "{title of option 1}", "{title of option 2}", @@ -57,7 +58,9 @@ void testMdGeneration() { "{title of option 1}", Arrays.asList( "Good, because {argument a}", - "Good, because {argument b}" + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" ) ).withDescription("{description}").withExample("{example}"), new MdOptionProsAndCons( @@ -68,78 +71,77 @@ void testMdGeneration() { "Neutral, because {argument c}", "Bad, because {argument d}" ) - ) + ).withDescription("{description}").withExample("{example}") ) ).withMoreInformation( "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" ).toString(), new IsEqual<>( - " ---\n" + - " status: accepted\n" + - " date: YYYY-MM-DD\n" + - " decision-makers: John Doe, Romain Rochegude\n" + - " consulted: John Doe, Romain Rochegude\n" + - " informed: John Doe, Romain Rochegude\n" + - " ---\n" + + "---\n" + + "status: accepted\n" + + "date: YYYY-MM-DD\n" + + "decision-makers: John Doe, Romain Rochegude\n" + + "consulted: John Doe, Romain Rochegude\n" + + "informed: John Doe, Romain Rochegude\n" + + "---\n" + "\n" + - " # {short title, representative of solved problem and found solution}\n" + + "# {short title, representative of solved problem and found solution}\n" + "\n" + - " ## Context and Problem Statement\n" + + "## Context and Problem Statement\n" + "\n" + - " {Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + + "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + "\n" + - " ## Decision Drivers\n" + + "## Decision Drivers\n" + "\n" + - " * {decision driver 1, e.g., a force, facing concern, ...}\n" + - " * {decision driver 2, e.g., a force, facing concern, ...}\n" + + "* {decision driver 1, e.g., a force, facing concern, ...}\n" + + "* {decision driver 2, e.g., a force, facing concern, ...}\n" + "\n" + - " ## Considered Options\n" + + "## Considered Options\n" + "\n" + - " * {title of option 1}\n" + - " * {title of option 2}\n" + - " * {title of option 3}\n" + + "* {title of option 1}\n" + + "* {title of option 2}\n" + + "* {title of option 3}\n" + "\n" + - " ## Decision Outcome\n" + + "## Decision Outcome\n" + "\n" + - " Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + + "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + "\n" + - " ### Consequences\n" + + "### Consequences\n" + "\n" + - " * Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + - " * Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + + "* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + + "* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + "\n" + - " ### Confirmation\n" + + "### Confirmation\n" + "\n" + - " {Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + + "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + "\n" + - " ## Pros and Cons of the Options\n" + + "## Pros and Cons of the Options\n" + "\n" + - " ### {title of option 1}\n" + + "### {title of option 1}\n" + "\n" + - " {description}\n" + + "{description}\n" + "\n" + - " {example}\n" + + "{example}\n" + "\n" + - " * Good, because {argument a}\n" + - " * Good, because {argument b}\n" + - " * Neutral, because {argument c}\n" + - " * Bad, because {argument d}\n" + + "* Good, because {argument a}\n" + + "* Good, because {argument b}\n" + + "* Neutral, because {argument c}\n" + + "* Bad, because {argument d}\n" + "\n" + - " ### {title of other option}\n" + + "### {title of other option}\n" + "\n" + - " {description}\n" + + "{description}\n" + "\n" + - " {example}\n" + + "{example}\n" + "\n" + - " * Good, because {argument a}\n" + - " * Good, because {argument b}\n" + - " * Neutral, because {argument c}\n" + - " * Bad, because {argument d}\n" + + "* Good, because {argument a}\n" + + "* Good, because {argument b}\n" + + "* Neutral, because {argument c}\n" + + "* Bad, because {argument d}\n" + "\n" + - " ## More Information\n" + + "## More Information\n" + "\n" + - " {You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" - + "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" ) ); } From 761830f29a3d6ae016cdbefa2dac18ad443f8e5c Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 23:36:33 +0100 Subject: [PATCH 06/17] implement toString methods to build a markdown ADR printable in the ArchRule reason Signed-off-by: Romain Rochegude --- .../com/tngtech/archunit/library/adr/Adr.java | 2 + .../library/adr/OptionProsAndCons.java | 8 ++-- .../archunit/library/adr/markdown/MdAdr.java | 39 ++++++++++++++++++- .../library/adr/markdown/MdMetadata.java | 12 ++++++ .../adr/markdown/MdOptionProsAndCons.java | 30 +++++++++----- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java index 8fab62ce40..80abf18280 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java @@ -27,6 +27,8 @@ public interface Adr { Adr withMetadata(final Metadata metadata); + String contextAndProblemStatement(); + String title(); Optional> decisionDrivers(); diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java index 91f2109e5a..825a4f76f6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java @@ -24,13 +24,13 @@ public interface OptionProsAndCons { String title(); - Optional example(); - - OptionProsAndCons withExample(final String example); - Optional description(); OptionProsAndCons withDescription(final String description); + Optional example(); + + OptionProsAndCons withExample(final String example); + List prosAndCons(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java index acd7b59f87..af6de54db5 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -21,11 +21,13 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; public final class MdAdr implements Adr { private Metadata metadata; private final String title; + private final String contextAndProblemStatement; private List decisionDrivers; private final List consideredOptions; private final String decisionOutcome; @@ -34,8 +36,9 @@ public final class MdAdr implements Adr { private List optionProsAndCons; private String moreInformation; - public MdAdr(final String title, final List consideredOptions, final String decisionOutcome) { + public MdAdr(final String title, final String contextAndProblemStatement, final List consideredOptions, final String decisionOutcome) { this.title = title; + this.contextAndProblemStatement = contextAndProblemStatement; this.consideredOptions = consideredOptions; this.decisionOutcome = decisionOutcome; } @@ -56,6 +59,11 @@ public String title() { return this.title; } + @Override + public String contextAndProblemStatement() { + return this.contextAndProblemStatement; + } + @Override public Optional> decisionDrivers() { return Optional.ofNullable(this.decisionDrivers); @@ -120,4 +128,33 @@ public Adr withMoreInformation(final String moreInformation) { this.moreInformation = moreInformation; return this; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + metadata().ifPresent(m -> sb.append(m).append("\n")); + sb.append("\n# ").append(title()).append("\n"); + sb.append("\n## Context and Problem Statement\n"); + sb.append("\n").append(contextAndProblemStatement()).append("\n"); + decisionDrivers().ifPresent(d -> { + sb.append("\n## Decision Drivers"); + sb.append("\n\n"); + d.forEach(s -> sb.append("* ").append(s).append("\n")); + }); + sb.append("\n## Considered Options\n\n"); + consideredOptions().forEach(o -> sb.append("* ").append(o).append("\n")); + sb.append("\n## Decision Outcome\n\n"); + sb.append(decisionOutcome()).append("\n"); + consequences().ifPresent(c -> { + sb.append("\n### Consequences\n\n"); + c.forEach(s -> sb.append("* ").append(s).append("\n")); + }); + confirmation().ifPresent(c -> sb.append("\n### Confirmation\n\n").append(c).append("\n")); + optionProsAndCons().ifPresent(opc -> { + sb.append("\n## Pros and Cons of the Options\n\n"); + opc.forEach(s -> sb.append(s.toString()).append("\n")); + }); + moreInformation().ifPresent(mi -> sb.append("## More Information\n\n").append(mi)); + return sb.toString(); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java index 092c3fece9..0c673d4b6c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java @@ -82,4 +82,16 @@ public Metadata withInformed(final List informed) { this.informed = informed; return this; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("---\n"); + status().ifPresent(s -> sb.append("status: ").append(s).append("\n")); + date().ifPresent(d -> sb.append("date: ").append(d).append("\n")); + decisionMakers().ifPresent(d -> sb.append("decision-makers: ").append(String.join(", ", d)).append("\n")); + consulted().ifPresent(c -> sb.append("consulted: ").append(String.join(", ", c)).append("\n")); + informed().ifPresent(i -> sb.append("informed: ").append(String.join(", ", i)).append("\n")); + sb.append("---"); + return sb.toString(); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java index 290872c3a4..539b0c3080 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -19,11 +19,12 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; public final class MdOptionProsAndCons implements OptionProsAndCons { private final String title; - private String example; private String description; + private String example; private final List prosAndCons; public MdOptionProsAndCons(final String title, final List prosAndCons) { @@ -37,24 +38,24 @@ public String title() { } @Override - public Optional example() { - return Optional.ofNullable(this.example); + public Optional description() { + return Optional.ofNullable(this.description); } @Override - public OptionProsAndCons withExample(final String example) { - this.example = example; + public OptionProsAndCons withDescription(final String description) { + this.description = description; return this; } @Override - public Optional description() { - return Optional.ofNullable(this.description); + public Optional example() { + return Optional.ofNullable(this.example); } @Override - public OptionProsAndCons withDescription(final String description) { - this.description = description; + public OptionProsAndCons withExample(final String example) { + this.example = example; return this; } @@ -62,4 +63,15 @@ public OptionProsAndCons withDescription(final String description) { public List prosAndCons() { return this.prosAndCons; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("### ").append(title()).append("\n\n"); + description().ifPresent(d -> sb.append(d).append("\n\n")); + example().ifPresent(e -> sb.append(e).append("\n\n")); + prosAndCons().forEach(pc -> + sb.append("* ").append(pc).append("\n") + ); + return sb.toString(); + } } From 5468aec7b316f1a712941605357a2ad84bd4abff Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sat, 2 Aug 2025 23:41:38 +0100 Subject: [PATCH 07/17] remove unused imports Signed-off-by: Romain Rochegude --- .../java/com/tngtech/archunit/library/adr/markdown/MdAdr.java | 1 - .../archunit/library/adr/markdown/MdOptionProsAndCons.java | 1 - 2 files changed, 2 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java index af6de54db5..b92d46499d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; public final class MdAdr implements Adr { diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java index 539b0c3080..f5470e7ce6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; public final class MdOptionProsAndCons implements OptionProsAndCons { private final String title; From cc8d91b7fea8df126c87412e3ea4db1702c015bc Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 00:05:56 +0100 Subject: [PATCH 08/17] Add Public API usage ACCESS where relevant Signed-off-by: Romain Rochegude --- .../com/tngtech/archunit/library/adr/Adr.java | 21 ++++++++++++++++++ .../archunit/library/adr/Metadata.java | 15 +++++++++++++ .../library/adr/OptionProsAndCons.java | 11 ++++++++++ .../archunit/library/adr/markdown/MdAdr.java | 22 +++++++++++++++++++ .../library/adr/markdown/MdMetadata.java | 18 +++++++++++++++ .../adr/markdown/MdOptionProsAndCons.java | 12 ++++++++++ 6 files changed, 99 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java index 80abf18280..52760ad76f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Adr.java @@ -15,43 +15,64 @@ */ package com.tngtech.archunit.library.adr; +import com.tngtech.archunit.PublicAPI; + import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + /** * Represents an Architecture Decision Record (ADR) * such as these templates. */ +@PublicAPI(usage = ACCESS) public interface Adr { + @PublicAPI(usage = ACCESS) Optional metadata(); + @PublicAPI(usage = ACCESS) Adr withMetadata(final Metadata metadata); + @PublicAPI(usage = ACCESS) String contextAndProblemStatement(); + @PublicAPI(usage = ACCESS) String title(); + @PublicAPI(usage = ACCESS) Optional> decisionDrivers(); + @PublicAPI(usage = ACCESS) Adr withDecisionDrivers(final List decisionDrivers); + @PublicAPI(usage = ACCESS) List consideredOptions(); + @PublicAPI(usage = ACCESS) String decisionOutcome(); + @PublicAPI(usage = ACCESS) Optional> consequences(); + @PublicAPI(usage = ACCESS) Adr withConsequences(final List consequences); + @PublicAPI(usage = ACCESS) Optional confirmation(); + @PublicAPI(usage = ACCESS) Adr withConfirmation(final String confirmation); + @PublicAPI(usage = ACCESS) Optional> optionProsAndCons(); + @PublicAPI(usage = ACCESS) Adr withOptionProsAndCons(final List optionProsAndCons); + @PublicAPI(usage = ACCESS) Optional moreInformation(); + @PublicAPI(usage = ACCESS) Adr withMoreInformation(final String moreInformation); } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java index 541628e30a..b363a622a9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/Metadata.java @@ -15,30 +15,45 @@ */ package com.tngtech.archunit.library.adr; +import com.tngtech.archunit.PublicAPI; + import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + /** * Metadata of the ADR. */ +@PublicAPI(usage = ACCESS) public interface Metadata { + @PublicAPI(usage = ACCESS) Optional status(); + @PublicAPI(usage = ACCESS) Metadata withStatus(final String status); + @PublicAPI(usage = ACCESS) Optional date(); + @PublicAPI(usage = ACCESS) Metadata withDate(final String date); + @PublicAPI(usage = ACCESS) Optional> decisionMakers(); + @PublicAPI(usage = ACCESS) Metadata withDecisionMakers(final List decisionMakers); + @PublicAPI(usage = ACCESS) Optional> consulted(); + @PublicAPI(usage = ACCESS) Metadata withConsulted(final List consulted); + @PublicAPI(usage = ACCESS) Optional> informed(); + @PublicAPI(usage = ACCESS) Metadata withInformed(final List informed); } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java index 825a4f76f6..9756840375 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/OptionProsAndCons.java @@ -15,22 +15,33 @@ */ package com.tngtech.archunit.library.adr; +import com.tngtech.archunit.PublicAPI; + import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + /** * Represents an option of an ADR with its pros and cons. */ +@PublicAPI(usage = ACCESS) public interface OptionProsAndCons { + @PublicAPI(usage = ACCESS) String title(); + @PublicAPI(usage = ACCESS) Optional description(); + @PublicAPI(usage = ACCESS) OptionProsAndCons withDescription(final String description); + @PublicAPI(usage = ACCESS) Optional example(); + @PublicAPI(usage = ACCESS) OptionProsAndCons withExample(final String example); + @PublicAPI(usage = ACCESS) List prosAndCons(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java index b92d46499d..0bf5cf650c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.library.adr.markdown; +import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.Adr; import com.tngtech.archunit.library.adr.Metadata; import com.tngtech.archunit.library.adr.OptionProsAndCons; @@ -22,6 +23,9 @@ import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) public final class MdAdr implements Adr { private Metadata metadata; @@ -35,6 +39,7 @@ public final class MdAdr implements Adr { private List optionProsAndCons; private String moreInformation; + @PublicAPI(usage = ACCESS) public MdAdr(final String title, final String contextAndProblemStatement, final List consideredOptions, final String decisionOutcome) { this.title = title; this.contextAndProblemStatement = contextAndProblemStatement; @@ -42,92 +47,109 @@ public MdAdr(final String title, final String contextAndProblemStatement, final this.decisionOutcome = decisionOutcome; } + @PublicAPI(usage = ACCESS) @Override public Optional metadata() { return Optional.ofNullable(this.metadata); } + @PublicAPI(usage = ACCESS) @Override public Adr withMetadata(final Metadata metadata) { this.metadata = metadata; return this; } + @PublicAPI(usage = ACCESS) @Override public String title() { return this.title; } + @PublicAPI(usage = ACCESS) @Override public String contextAndProblemStatement() { return this.contextAndProblemStatement; } + @PublicAPI(usage = ACCESS) @Override public Optional> decisionDrivers() { return Optional.ofNullable(this.decisionDrivers); } + @PublicAPI(usage = ACCESS) @Override public Adr withDecisionDrivers(final List decisionDrivers) { this.decisionDrivers = decisionDrivers; return this; } + @PublicAPI(usage = ACCESS) @Override public List consideredOptions() { return this.consideredOptions; } + @PublicAPI(usage = ACCESS) @Override public String decisionOutcome() { return this.decisionOutcome; } + @PublicAPI(usage = ACCESS) @Override public Optional> consequences() { return Optional.ofNullable(this.consequences); } + @PublicAPI(usage = ACCESS) @Override public Adr withConsequences(final List consequences) { this.consequences = consequences; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional confirmation() { return Optional.ofNullable(this.confirmation); } + @PublicAPI(usage = ACCESS) @Override public Adr withConfirmation(final String confirmation) { this.confirmation = confirmation; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional> optionProsAndCons() { return Optional.ofNullable(this.optionProsAndCons); } + @PublicAPI(usage = ACCESS) @Override public Adr withOptionProsAndCons(final List optionProsAndCons) { this.optionProsAndCons = optionProsAndCons; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional moreInformation() { return Optional.ofNullable(this.moreInformation); } + @PublicAPI(usage = ACCESS) @Override public Adr withMoreInformation(final String moreInformation) { this.moreInformation = moreInformation; return this; } + @PublicAPI(usage = ACCESS) @Override public String toString() { final StringBuilder sb = new StringBuilder(); diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java index 0c673d4b6c..74a30fd488 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java @@ -15,11 +15,15 @@ */ package com.tngtech.archunit.library.adr.markdown; +import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.Metadata; import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) public final class MdMetadata implements Metadata { private String status; @@ -28,61 +32,75 @@ public final class MdMetadata implements Metadata { private List consulted; private List informed; + @PublicAPI(usage = ACCESS) + public MdMetadata() {} + + @PublicAPI(usage = ACCESS) @Override public Optional status() { return Optional.ofNullable(this.status); } + @PublicAPI(usage = ACCESS) @Override public Metadata withStatus(final String status) { this.status = status; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional date() { return Optional.ofNullable(this.date); } + @PublicAPI(usage = ACCESS) @Override public Metadata withDate(final String date) { this.date = date; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional> decisionMakers() { return Optional.ofNullable(this.decisionMakers); } + @PublicAPI(usage = ACCESS) @Override public Metadata withDecisionMakers(final List decisionMakers) { this.decisionMakers = decisionMakers; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional> consulted() { return Optional.ofNullable(this.consulted); } + @PublicAPI(usage = ACCESS) @Override public Metadata withConsulted(final List consulted) { this.consulted = consulted; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional> informed() { return Optional.ofNullable(this.informed); } + @PublicAPI(usage = ACCESS) @Override public Metadata withInformed(final List informed) { this.informed = informed; return this; } + @PublicAPI(usage = ACCESS) @Override public String toString() { final StringBuilder sb = new StringBuilder("---\n"); diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java index f5470e7ce6..22a0b0f607 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -15,54 +15,66 @@ */ package com.tngtech.archunit.library.adr.markdown; +import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.OptionProsAndCons; import java.util.List; import java.util.Optional; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) public final class MdOptionProsAndCons implements OptionProsAndCons { private final String title; private String description; private String example; private final List prosAndCons; + @PublicAPI(usage = ACCESS) public MdOptionProsAndCons(final String title, final List prosAndCons) { this.title = title; this.prosAndCons = prosAndCons; } + @PublicAPI(usage = ACCESS) @Override public String title() { return this.title; } + @PublicAPI(usage = ACCESS) @Override public Optional description() { return Optional.ofNullable(this.description); } + @PublicAPI(usage = ACCESS) @Override public OptionProsAndCons withDescription(final String description) { this.description = description; return this; } + @PublicAPI(usage = ACCESS) @Override public Optional example() { return Optional.ofNullable(this.example); } + @PublicAPI(usage = ACCESS) @Override public OptionProsAndCons withExample(final String example) { this.example = example; return this; } + @PublicAPI(usage = ACCESS) @Override public List prosAndCons() { return this.prosAndCons; } + @PublicAPI(usage = ACCESS) @Override public String toString() { final StringBuilder sb = new StringBuilder("### ").append(title()).append("\n\n"); From 7cc282b57bffc464e8cd5120fbc9f20311bbd103 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 01:04:20 +0100 Subject: [PATCH 09/17] declare envlopes to ease the decorator pattern Signed-off-by: Romain Rochegude --- .../library/adr/envelopes/AdrEnvelope.java | 133 ++++++++++++++++++ .../adr/envelopes/MetadataEnvelope.java | 95 +++++++++++++ .../envelopes/OptionProsAndConsEnvelope.java | 71 ++++++++++ 3 files changed, 299 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java new file mode 100644 index 0000000000..b24ca5116b --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java @@ -0,0 +1,133 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.envelopes; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.Adr; +import com.tngtech.archunit.library.adr.Metadata; +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; + +@PublicAPI(usage = INHERITANCE) +public abstract class AdrEnvelope implements Adr { + private final Adr delegate; + + @PublicAPI(usage = ACCESS) + public AdrEnvelope(final Adr delegate) { + this.delegate = delegate; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional metadata() { + return this.delegate.metadata(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withMetadata(final Metadata metadata) { + return this.delegate.withMetadata(metadata); + } + + @PublicAPI(usage = ACCESS) + @Override + public String contextAndProblemStatement() { + return this.delegate.contextAndProblemStatement(); + } + + @PublicAPI(usage = ACCESS) + @Override + public String title() { + return this.delegate.title(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> decisionDrivers() { + return this.delegate.decisionDrivers(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withDecisionDrivers(final List decisionDrivers) { + return this.delegate.withDecisionDrivers(decisionDrivers); + } + + @PublicAPI(usage = ACCESS) + @Override + public List consideredOptions() { + return this.delegate.consideredOptions(); + } + + @PublicAPI(usage = ACCESS) + @Override + public String decisionOutcome() { + return this.delegate.decisionOutcome(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> consequences() { + return this.delegate.consequences(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withConsequences(final List consequences) { + return this.delegate.withConsequences(consequences); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional confirmation() { + return this.delegate.confirmation(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withConfirmation(final String confirmation) { + return this.delegate.withConfirmation(confirmation); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> optionProsAndCons() { + return this.delegate.optionProsAndCons(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withOptionProsAndCons(final List optionProsAndCons) { + return this.delegate.withOptionProsAndCons(optionProsAndCons); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional moreInformation() { + return this.delegate.moreInformation(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withMoreInformation(final String moreInformation) { + return this.delegate.withMoreInformation(moreInformation); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java new file mode 100644 index 0000000000..a63c77d79e --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java @@ -0,0 +1,95 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.envelopes; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.Metadata; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; + +@PublicAPI(usage = INHERITANCE) +public abstract class MetadataEnvelope implements Metadata { + private final Metadata delegate; + + @PublicAPI(usage = ACCESS) + public MetadataEnvelope(final Metadata delegate) { + this.delegate = delegate; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional status() { + return this.delegate.status(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withStatus(final String status) { + return this.delegate.withStatus(status); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional date() { + return this.delegate.date(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withDate(final String date) { + return this.delegate.withDate(date); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> decisionMakers() { + return this.delegate.decisionMakers(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withDecisionMakers(final List decisionMakers) { + return this.delegate.withDecisionMakers(decisionMakers); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> consulted() { + return this.delegate.consulted(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withConsulted(final List consulted) { + return this.delegate.withConsulted(consulted); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> informed() { + return this.delegate.informed(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withInformed(final List informed) { + return this.delegate.withInformed(informed); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java new file mode 100644 index 0000000000..7f5c9ca38b --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.envelopes; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; + +@PublicAPI(usage = INHERITANCE) +public abstract class OptionProsAndConsEnvelope implements OptionProsAndCons { + private final OptionProsAndCons delegate; + + @PublicAPI(usage = ACCESS) + public OptionProsAndConsEnvelope(final OptionProsAndCons delegate) { + this.delegate = delegate; + } + + @PublicAPI(usage = ACCESS) + @Override + public String title() { + return this.delegate.title(); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional description() { + return this.delegate.description(); + } + + @PublicAPI(usage = ACCESS) + @Override + public OptionProsAndCons withDescription(final String description) { + return this.delegate.withDescription(description); + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional example() { + return this.delegate.example(); + } + + @PublicAPI(usage = ACCESS) + @Override + public OptionProsAndCons withExample(final String example) { + return this.delegate.withExample(example); + } + + @PublicAPI(usage = ACCESS) + @Override + public List prosAndCons() { + return this.delegate.prosAndCons(); + } +} From a2c10757d923746f18364e4e926022b645a6fa74 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 01:04:38 +0100 Subject: [PATCH 10/17] provide simple implementation of ADR contracts Signed-off-by: Romain Rochegude --- .../library/adr/simples/SimpleAdr.java | 151 ++++++++++++++++++ .../library/adr/simples/SimpleMetadata.java | 102 ++++++++++++ .../adr/simples/SimpleOptionProsAndCons.java | 76 +++++++++ 3 files changed, 329 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleAdr.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleMetadata.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleOptionProsAndCons.java diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleAdr.java new file mode 100644 index 0000000000..977d5871cd --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleAdr.java @@ -0,0 +1,151 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.simples; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.Adr; +import com.tngtech.archunit.library.adr.Metadata; +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public final class SimpleAdr implements Adr { + + private Metadata metadata; + private final String title; + private final String contextAndProblemStatement; + private List decisionDrivers; + private final List consideredOptions; + private final String decisionOutcome; + private List consequences; + private String confirmation; + private List optionProsAndCons; + private String moreInformation; + + @PublicAPI(usage = ACCESS) + public SimpleAdr(final String title, final String contextAndProblemStatement, final List consideredOptions, final String decisionOutcome) { + this.title = title; + this.contextAndProblemStatement = contextAndProblemStatement; + this.consideredOptions = consideredOptions; + this.decisionOutcome = decisionOutcome; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional metadata() { + return Optional.ofNullable(this.metadata); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withMetadata(final Metadata metadata) { + this.metadata = metadata; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public String title() { + return this.title; + } + + @PublicAPI(usage = ACCESS) + @Override + public String contextAndProblemStatement() { + return this.contextAndProblemStatement; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> decisionDrivers() { + return Optional.ofNullable(this.decisionDrivers); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withDecisionDrivers(final List decisionDrivers) { + this.decisionDrivers = decisionDrivers; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public List consideredOptions() { + return this.consideredOptions; + } + + @PublicAPI(usage = ACCESS) + @Override + public String decisionOutcome() { + return this.decisionOutcome; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> consequences() { + return Optional.ofNullable(this.consequences); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withConsequences(final List consequences) { + this.consequences = consequences; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional confirmation() { + return Optional.ofNullable(this.confirmation); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withConfirmation(final String confirmation) { + this.confirmation = confirmation; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> optionProsAndCons() { + return Optional.ofNullable(this.optionProsAndCons); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withOptionProsAndCons(final List optionProsAndCons) { + this.optionProsAndCons = optionProsAndCons; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional moreInformation() { + return Optional.ofNullable(this.moreInformation); + } + + @PublicAPI(usage = ACCESS) + @Override + public Adr withMoreInformation(final String moreInformation) { + this.moreInformation = moreInformation; + return this; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleMetadata.java new file mode 100644 index 0000000000..2f6d9bf059 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleMetadata.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.simples; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.Metadata; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public final class SimpleMetadata implements Metadata { + + private String status; + private String date; + private List decisionMakers; + private List consulted; + private List informed; + + @PublicAPI(usage = ACCESS) + public SimpleMetadata() {} + + @PublicAPI(usage = ACCESS) + @Override + public Optional status() { + return Optional.ofNullable(this.status); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withStatus(final String status) { + this.status = status; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional date() { + return Optional.ofNullable(this.date); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withDate(final String date) { + this.date = date; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> decisionMakers() { + return Optional.ofNullable(this.decisionMakers); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withDecisionMakers(final List decisionMakers) { + this.decisionMakers = decisionMakers; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> consulted() { + return Optional.ofNullable(this.consulted); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withConsulted(final List consulted) { + this.consulted = consulted; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional> informed() { + return Optional.ofNullable(this.informed); + } + + @PublicAPI(usage = ACCESS) + @Override + public Metadata withInformed(final List informed) { + this.informed = informed; + return this; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleOptionProsAndCons.java new file mode 100644 index 0000000000..ecb33604a8 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/simples/SimpleOptionProsAndCons.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014-2025 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.library.adr.simples; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.library.adr.OptionProsAndCons; + +import java.util.List; +import java.util.Optional; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public final class SimpleOptionProsAndCons implements OptionProsAndCons { + private final String title; + private String description; + private String example; + private final List prosAndCons; + + @PublicAPI(usage = ACCESS) + public SimpleOptionProsAndCons(final String title, final List prosAndCons) { + this.title = title; + this.prosAndCons = prosAndCons; + } + + @PublicAPI(usage = ACCESS) + @Override + public String title() { + return this.title; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional description() { + return Optional.ofNullable(this.description); + } + + @PublicAPI(usage = ACCESS) + @Override + public OptionProsAndCons withDescription(final String description) { + this.description = description; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public Optional example() { + return Optional.ofNullable(this.example); + } + + @PublicAPI(usage = ACCESS) + @Override + public OptionProsAndCons withExample(final String example) { + this.example = example; + return this; + } + + @PublicAPI(usage = ACCESS) + @Override + public List prosAndCons() { + return this.prosAndCons; + } +} From e97e226d7a004ca26c4f8e7b5dc776f645b9c05b Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 01:04:58 +0100 Subject: [PATCH 11/17] refactor to split concerns and make the test pass Signed-off-by: Romain Rochegude --- .../archunit/library/adr/markdown/MdAdr.java | 128 +----------------- .../library/adr/markdown/MdMetadata.java | 79 +---------- .../adr/markdown/MdOptionProsAndCons.java | 53 +------- .../library/adr/markdown/MdAdrTest.java | 123 +++++++++-------- 4 files changed, 79 insertions(+), 304 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java index 0bf5cf650c..fa9878f9e4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdAdr.java @@ -17,136 +17,16 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.Adr; -import com.tngtech.archunit.library.adr.Metadata; -import com.tngtech.archunit.library.adr.OptionProsAndCons; - -import java.util.List; -import java.util.Optional; +import com.tngtech.archunit.library.adr.envelopes.AdrEnvelope; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; @PublicAPI(usage = ACCESS) -public final class MdAdr implements Adr { - - private Metadata metadata; - private final String title; - private final String contextAndProblemStatement; - private List decisionDrivers; - private final List consideredOptions; - private final String decisionOutcome; - private List consequences; - private String confirmation; - private List optionProsAndCons; - private String moreInformation; - - @PublicAPI(usage = ACCESS) - public MdAdr(final String title, final String contextAndProblemStatement, final List consideredOptions, final String decisionOutcome) { - this.title = title; - this.contextAndProblemStatement = contextAndProblemStatement; - this.consideredOptions = consideredOptions; - this.decisionOutcome = decisionOutcome; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional metadata() { - return Optional.ofNullable(this.metadata); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withMetadata(final Metadata metadata) { - this.metadata = metadata; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public String title() { - return this.title; - } - - @PublicAPI(usage = ACCESS) - @Override - public String contextAndProblemStatement() { - return this.contextAndProblemStatement; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> decisionDrivers() { - return Optional.ofNullable(this.decisionDrivers); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withDecisionDrivers(final List decisionDrivers) { - this.decisionDrivers = decisionDrivers; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public List consideredOptions() { - return this.consideredOptions; - } +public final class MdAdr extends AdrEnvelope { @PublicAPI(usage = ACCESS) - @Override - public String decisionOutcome() { - return this.decisionOutcome; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> consequences() { - return Optional.ofNullable(this.consequences); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withConsequences(final List consequences) { - this.consequences = consequences; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional confirmation() { - return Optional.ofNullable(this.confirmation); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withConfirmation(final String confirmation) { - this.confirmation = confirmation; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> optionProsAndCons() { - return Optional.ofNullable(this.optionProsAndCons); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withOptionProsAndCons(final List optionProsAndCons) { - this.optionProsAndCons = optionProsAndCons; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional moreInformation() { - return Optional.ofNullable(this.moreInformation); - } - - @PublicAPI(usage = ACCESS) - @Override - public Adr withMoreInformation(final String moreInformation) { - this.moreInformation = moreInformation; - return this; + public MdAdr(final Adr delegate) { + super(delegate); } @PublicAPI(usage = ACCESS) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java index 74a30fd488..5d86193c37 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdMetadata.java @@ -17,87 +17,16 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.Metadata; - -import java.util.List; -import java.util.Optional; +import com.tngtech.archunit.library.adr.envelopes.MetadataEnvelope; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; @PublicAPI(usage = ACCESS) -public final class MdMetadata implements Metadata { - - private String status; - private String date; - private List decisionMakers; - private List consulted; - private List informed; - - @PublicAPI(usage = ACCESS) - public MdMetadata() {} +public final class MdMetadata extends MetadataEnvelope { @PublicAPI(usage = ACCESS) - @Override - public Optional status() { - return Optional.ofNullable(this.status); - } - - @PublicAPI(usage = ACCESS) - @Override - public Metadata withStatus(final String status) { - this.status = status; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional date() { - return Optional.ofNullable(this.date); - } - - @PublicAPI(usage = ACCESS) - @Override - public Metadata withDate(final String date) { - this.date = date; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> decisionMakers() { - return Optional.ofNullable(this.decisionMakers); - } - - @PublicAPI(usage = ACCESS) - @Override - public Metadata withDecisionMakers(final List decisionMakers) { - this.decisionMakers = decisionMakers; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> consulted() { - return Optional.ofNullable(this.consulted); - } - - @PublicAPI(usage = ACCESS) - @Override - public Metadata withConsulted(final List consulted) { - this.consulted = consulted; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional> informed() { - return Optional.ofNullable(this.informed); - } - - @PublicAPI(usage = ACCESS) - @Override - public Metadata withInformed(final List informed) { - this.informed = informed; - return this; + public MdMetadata(final Metadata delegate) { + super(delegate); } @PublicAPI(usage = ACCESS) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java index 22a0b0f607..078e956648 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/markdown/MdOptionProsAndCons.java @@ -17,61 +17,16 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.library.adr.OptionProsAndCons; - -import java.util.List; -import java.util.Optional; +import com.tngtech.archunit.library.adr.envelopes.OptionProsAndConsEnvelope; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; @PublicAPI(usage = ACCESS) -public final class MdOptionProsAndCons implements OptionProsAndCons { - private final String title; - private String description; - private String example; - private final List prosAndCons; - - @PublicAPI(usage = ACCESS) - public MdOptionProsAndCons(final String title, final List prosAndCons) { - this.title = title; - this.prosAndCons = prosAndCons; - } +public final class MdOptionProsAndCons extends OptionProsAndConsEnvelope { @PublicAPI(usage = ACCESS) - @Override - public String title() { - return this.title; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional description() { - return Optional.ofNullable(this.description); - } - - @PublicAPI(usage = ACCESS) - @Override - public OptionProsAndCons withDescription(final String description) { - this.description = description; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public Optional example() { - return Optional.ofNullable(this.example); - } - - @PublicAPI(usage = ACCESS) - @Override - public OptionProsAndCons withExample(final String example) { - this.example = example; - return this; - } - - @PublicAPI(usage = ACCESS) - @Override - public List prosAndCons() { - return this.prosAndCons; + public MdOptionProsAndCons(final OptionProsAndCons delegate) { + super(delegate); } @PublicAPI(usage = ACCESS) diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java index fb82dc37e2..270fc7c8c0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -1,5 +1,8 @@ package com.tngtech.archunit.library.adr.markdown; +import com.tngtech.archunit.library.adr.simples.SimpleAdr; +import com.tngtech.archunit.library.adr.simples.SimpleMetadata; +import com.tngtech.archunit.library.adr.simples.SimpleOptionProsAndCons; import org.hamcrest.MatcherAssert; import org.hamcrest.core.IsEqual; import org.junit.jupiter.api.Test; @@ -11,70 +14,78 @@ class MdAdrTest { void testMdGeneration() { MatcherAssert.assertThat( new MdAdr( - "{short title, representative of solved problem and found solution}", - "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}", - Arrays.asList( - "{title of option 1}", - "{title of option 2}", - "{title of option 3}" - ), - "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}." - ).withMetadata( - new MdMetadata().withStatus( - "accepted" - ).withDate( - "YYYY-MM-DD" - ).withDecisionMakers( + new SimpleAdr( + "{short title, representative of solved problem and found solution}", + "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}", Arrays.asList( - "John Doe", - "Romain Rochegude" + "{title of option 1}", + "{title of option 2}", + "{title of option 3}" + ), + "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}." + ).withMetadata( + new MdMetadata( + new SimpleMetadata().withStatus( + "accepted" + ).withDate( + "YYYY-MM-DD" + ).withDecisionMakers( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withConsulted( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withInformed( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ) ) - ).withConsulted( + ).withDecisionDrivers( Arrays.asList( - "John Doe", - "Romain Rochegude" + "{decision driver 1, e.g., a force, facing concern, ...}", + "{decision driver 2, e.g., a force, facing concern, ...}" ) - ).withInformed( + ).withConsequences( Arrays.asList( - "John Doe", - "Romain Rochegude" + "Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}", + "Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}" ) - ) - ).withDecisionDrivers( - Arrays.asList( - "{decision driver 1, e.g., a force, facing concern, ...}", - "{decision driver 2, e.g., a force, facing concern, ...}" - ) - ).withConsequences( - Arrays.asList( - "Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}", - "Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}" - ) - ).withConfirmation( - "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}" - ).withOptionProsAndCons( - Arrays.asList( - new MdOptionProsAndCons( - "{title of option 1}", - Arrays.asList( - "Good, because {argument a}", - "Good, because {argument b}", - "Neutral, because {argument c}", - "Bad, because {argument d}" - ) - ).withDescription("{description}").withExample("{example}"), - new MdOptionProsAndCons( - "{title of other option}", - Arrays.asList( - "Good, because {argument a}", - "Good, because {argument b}", - "Neutral, because {argument c}", - "Bad, because {argument d}" + ).withConfirmation( + "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}" + ).withOptionProsAndCons( + Arrays.asList( + new MdOptionProsAndCons( + new SimpleOptionProsAndCons( + "{title of option 1}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" + ) + ).withDescription("{description}").withExample("{example}") + ), + new MdOptionProsAndCons( + new SimpleOptionProsAndCons( + "{title of other option}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" + ) + ).withDescription("{description}").withExample("{example}") ) - ).withDescription("{description}").withExample("{example}") + ) + ).withMoreInformation( + "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" ) - ).withMoreInformation( - "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" ).toString(), new IsEqual<>( "---\n" + From 97a319f320797c34ac69a6e9baa0f78d96e5c934 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 09:29:17 +0100 Subject: [PATCH 12/17] extract ADR and refactor test Signed-off-by: Romain Rochegude --- .../library/adr/markdown/ExampleAdr.java | 89 +++++++++++++++++++ .../library/adr/markdown/MdAdrTest.java | 80 +---------------- 2 files changed, 90 insertions(+), 79 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExampleAdr.java diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExampleAdr.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExampleAdr.java new file mode 100644 index 0000000000..3954e0ea67 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExampleAdr.java @@ -0,0 +1,89 @@ +package com.tngtech.archunit.library.adr.markdown; + +import com.tngtech.archunit.library.adr.envelopes.AdrEnvelope; +import com.tngtech.archunit.library.adr.simples.SimpleAdr; +import com.tngtech.archunit.library.adr.simples.SimpleMetadata; +import com.tngtech.archunit.library.adr.simples.SimpleOptionProsAndCons; + +import java.util.Arrays; + +public final class ExampleAdr extends AdrEnvelope { + public ExampleAdr() { + super( + new MdAdr( + new SimpleAdr( + "{short title, representative of solved problem and found solution}", + "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}", + Arrays.asList( + "{title of option 1}", + "{title of option 2}", + "{title of option 3}" + ), + "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}." + ).withMetadata( + new MdMetadata( + new SimpleMetadata().withStatus( + "accepted" + ).withDate( + "YYYY-MM-DD" + ).withDecisionMakers( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withConsulted( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ).withInformed( + Arrays.asList( + "John Doe", + "Romain Rochegude" + ) + ) + ) + ).withDecisionDrivers( + Arrays.asList( + "{decision driver 1, e.g., a force, facing concern, ...}", + "{decision driver 2, e.g., a force, facing concern, ...}" + ) + ).withConsequences( + Arrays.asList( + "Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}", + "Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}" + ) + ).withConfirmation( + "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}" + ).withOptionProsAndCons( + Arrays.asList( + new MdOptionProsAndCons( + new SimpleOptionProsAndCons( + "{title of option 1}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" + ) + ).withDescription("{description}").withExample("{example}") + ), + new MdOptionProsAndCons( + new SimpleOptionProsAndCons( + "{title of other option}", + Arrays.asList( + "Good, because {argument a}", + "Good, because {argument b}", + "Neutral, because {argument c}", + "Bad, because {argument d}" + ) + ).withDescription("{description}").withExample("{example}") + ) + ) + ).withMoreInformation( + "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" + ) + ) + ); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java index 270fc7c8c0..8bfdc04c2b 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -1,92 +1,14 @@ package com.tngtech.archunit.library.adr.markdown; -import com.tngtech.archunit.library.adr.simples.SimpleAdr; -import com.tngtech.archunit.library.adr.simples.SimpleMetadata; -import com.tngtech.archunit.library.adr.simples.SimpleOptionProsAndCons; import org.hamcrest.MatcherAssert; import org.hamcrest.core.IsEqual; import org.junit.jupiter.api.Test; -import java.util.Arrays; - class MdAdrTest { @Test void testMdGeneration() { MatcherAssert.assertThat( - new MdAdr( - new SimpleAdr( - "{short title, representative of solved problem and found solution}", - "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}", - Arrays.asList( - "{title of option 1}", - "{title of option 2}", - "{title of option 3}" - ), - "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}." - ).withMetadata( - new MdMetadata( - new SimpleMetadata().withStatus( - "accepted" - ).withDate( - "YYYY-MM-DD" - ).withDecisionMakers( - Arrays.asList( - "John Doe", - "Romain Rochegude" - ) - ).withConsulted( - Arrays.asList( - "John Doe", - "Romain Rochegude" - ) - ).withInformed( - Arrays.asList( - "John Doe", - "Romain Rochegude" - ) - ) - ) - ).withDecisionDrivers( - Arrays.asList( - "{decision driver 1, e.g., a force, facing concern, ...}", - "{decision driver 2, e.g., a force, facing concern, ...}" - ) - ).withConsequences( - Arrays.asList( - "Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}", - "Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}" - ) - ).withConfirmation( - "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}" - ).withOptionProsAndCons( - Arrays.asList( - new MdOptionProsAndCons( - new SimpleOptionProsAndCons( - "{title of option 1}", - Arrays.asList( - "Good, because {argument a}", - "Good, because {argument b}", - "Neutral, because {argument c}", - "Bad, because {argument d}" - ) - ).withDescription("{description}").withExample("{example}") - ), - new MdOptionProsAndCons( - new SimpleOptionProsAndCons( - "{title of other option}", - Arrays.asList( - "Good, because {argument a}", - "Good, because {argument b}", - "Neutral, because {argument c}", - "Bad, because {argument d}" - ) - ).withDescription("{description}").withExample("{example}") - ) - ) - ).withMoreInformation( - "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" - ) - ).toString(), + new ExampleAdr().toString(), new IsEqual<>( "---\n" + "status: accepted\n" + From d8b6848ec35e7fb6983c185a6fe6b21d34f05177 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 09:30:04 +0100 Subject: [PATCH 13/17] override toString method in envelopes to invoke delegate's ones Signed-off-by: Romain Rochegude --- .../tngtech/archunit/library/adr/envelopes/AdrEnvelope.java | 6 ++++++ .../archunit/library/adr/envelopes/MetadataEnvelope.java | 6 ++++++ .../library/adr/envelopes/OptionProsAndConsEnvelope.java | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java index b24ca5116b..1b7c8ce5e2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/AdrEnvelope.java @@ -130,4 +130,10 @@ public Optional moreInformation() { public Adr withMoreInformation(final String moreInformation) { return this.delegate.withMoreInformation(moreInformation); } + + @PublicAPI(usage = ACCESS) + @Override + public String toString() { + return this.delegate.toString(); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java index a63c77d79e..47a82ddbea 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/MetadataEnvelope.java @@ -92,4 +92,10 @@ public Optional> informed() { public Metadata withInformed(final List informed) { return this.delegate.withInformed(informed); } + + @PublicAPI(usage = ACCESS) + @Override + public String toString() { + return this.delegate.toString(); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java index 7f5c9ca38b..8a59c09990 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/adr/envelopes/OptionProsAndConsEnvelope.java @@ -68,4 +68,10 @@ public OptionProsAndCons withExample(final String example) { public List prosAndCons() { return this.delegate.prosAndCons(); } + + @PublicAPI(usage = ACCESS) + @Override + public String toString() { + return this.delegate.toString(); + } } From e37170539f92f81f19c94488b64b4351f2619033 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 09:36:51 +0100 Subject: [PATCH 14/17] extract expected example ADR Signed-off-by: Romain Rochegude --- .../adr/markdown/ExpectedExampleAdr.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java new file mode 100644 index 0000000000..554b73a585 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java @@ -0,0 +1,72 @@ +package com.tngtech.archunit.library.adr.markdown; + +public final class ExpectedExampleAdr { + @Override + public String toString() { + return "---\n" + + "status: accepted\n" + + "date: YYYY-MM-DD\n" + + "decision-makers: John Doe, Romain Rochegude\n" + + "consulted: John Doe, Romain Rochegude\n" + + "informed: John Doe, Romain Rochegude\n" + + "---\n" + + "\n" + + "# {short title, representative of solved problem and found solution}\n" + + "\n" + + "## Context and Problem Statement\n" + + "\n" + + "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + + "\n" + + "## Decision Drivers\n" + + "\n" + + "* {decision driver 1, e.g., a force, facing concern, ...}\n" + + "* {decision driver 2, e.g., a force, facing concern, ...}\n" + + "\n" + + "## Considered Options\n" + + "\n" + + "* {title of option 1}\n" + + "* {title of option 2}\n" + + "* {title of option 3}\n" + + "\n" + + "## Decision Outcome\n" + + "\n" + + "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + + "\n" + + "### Consequences\n" + + "\n" + + "* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + + "* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + + "\n" + + "### Confirmation\n" + + "\n" + + "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + + "\n" + + "## Pros and Cons of the Options\n" + + "\n" + + "### {title of option 1}\n" + + "\n" + + "{description}\n" + + "\n" + + "{example}\n" + + "\n" + + "* Good, because {argument a}\n" + + "* Good, because {argument b}\n" + + "* Neutral, because {argument c}\n" + + "* Bad, because {argument d}\n" + + "\n" + + "### {title of other option}\n" + + "\n" + + "{description}\n" + + "\n" + + "{example}\n" + + "\n" + + "* Good, because {argument a}\n" + + "* Good, because {argument b}\n" + + "* Neutral, because {argument c}\n" + + "* Bad, because {argument d}\n" + + "\n" + + "## More Information\n" + + "\n" + + "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}"; + } +} From d90d4203ff17a9f940dfee5d71247d83f752b0c6 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 09:37:01 +0100 Subject: [PATCH 15/17] refactor test Signed-off-by: Romain Rochegude --- .../library/adr/markdown/MdAdrTest.java | 66 +------------------ 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java index 8bfdc04c2b..433a06f709 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -10,71 +10,7 @@ void testMdGeneration() { MatcherAssert.assertThat( new ExampleAdr().toString(), new IsEqual<>( - "---\n" + - "status: accepted\n" + - "date: YYYY-MM-DD\n" + - "decision-makers: John Doe, Romain Rochegude\n" + - "consulted: John Doe, Romain Rochegude\n" + - "informed: John Doe, Romain Rochegude\n" + - "---\n" + - "\n" + - "# {short title, representative of solved problem and found solution}\n" + - "\n" + - "## Context and Problem Statement\n" + - "\n" + - "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + - "\n" + - "## Decision Drivers\n" + - "\n" + - "* {decision driver 1, e.g., a force, facing concern, ...}\n" + - "* {decision driver 2, e.g., a force, facing concern, ...}\n" + - "\n" + - "## Considered Options\n" + - "\n" + - "* {title of option 1}\n" + - "* {title of option 2}\n" + - "* {title of option 3}\n" + - "\n" + - "## Decision Outcome\n" + - "\n" + - "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + - "\n" + - "### Consequences\n" + - "\n" + - "* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + - "* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + - "\n" + - "### Confirmation\n" + - "\n" + - "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + - "\n" + - "## Pros and Cons of the Options\n" + - "\n" + - "### {title of option 1}\n" + - "\n" + - "{description}\n" + - "\n" + - "{example}\n" + - "\n" + - "* Good, because {argument a}\n" + - "* Good, because {argument b}\n" + - "* Neutral, because {argument c}\n" + - "* Bad, because {argument d}\n" + - "\n" + - "### {title of other option}\n" + - "\n" + - "{description}\n" + - "\n" + - "{example}\n" + - "\n" + - "* Good, because {argument a}\n" + - "* Good, because {argument b}\n" + - "* Neutral, because {argument c}\n" + - "* Bad, because {argument d}\n" + - "\n" + - "## More Information\n" + - "\n" + - "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}" + new ExpectedExampleAdr().toString() ) ); } From e58f2bbe23daeab26b1a313a119b3c88d929c39a Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 10:26:26 +0100 Subject: [PATCH 16/17] extract expected markdown file in resource directory Signed-off-by: Romain Rochegude --- .../resources/data/expected_example_adr.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 archunit/src/test/resources/data/expected_example_adr.md diff --git a/archunit/src/test/resources/data/expected_example_adr.md b/archunit/src/test/resources/data/expected_example_adr.md new file mode 100644 index 0000000000..a85bd92f5c --- /dev/null +++ b/archunit/src/test/resources/data/expected_example_adr.md @@ -0,0 +1,65 @@ +--- +status: accepted +date: YYYY-MM-DD +decision-makers: John Doe, Romain Rochegude +consulted: John Doe, Romain Rochegude +informed: John Doe, Romain Rochegude +--- + +# {short title, representative of solved problem and found solution} + +## Context and Problem Statement + +{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} + +## Decision Drivers + +* {decision driver 1, e.g., a force, facing concern, ...} +* {decision driver 2, e.g., a force, facing concern, ...} + +## Considered Options + +* {title of option 1} +* {title of option 2} +* {title of option 3} + +## Decision Outcome + +Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}. + +### Consequences + +* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...} +* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...} + +### Confirmation + +{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.} + +## Pros and Cons of the Options + +### {title of option 1} + +{description} + +{example} + +* Good, because {argument a} +* Good, because {argument b} +* Neutral, because {argument c} +* Bad, because {argument d} + +### {title of other option} + +{description} + +{example} + +* Good, because {argument a} +* Good, because {argument b} +* Neutral, because {argument c} +* Bad, because {argument d} + +## More Information + +{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.} \ No newline at end of file From ac65868fcaefbf4b125f718b333eaa00d2dfff40 Mon Sep 17 00:00:00 2001 From: Romain Rochegude Date: Sun, 3 Aug 2025 10:26:42 +0100 Subject: [PATCH 17/17] refactor test to load expected markdown from file Signed-off-by: Romain Rochegude --- .../adr/markdown/ExpectedExampleAdr.java | 87 +++++-------------- .../library/adr/markdown/MdAdrTest.java | 2 +- 2 files changed, 23 insertions(+), 66 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java index 554b73a585..0562e76fc1 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/ExpectedExampleAdr.java @@ -1,72 +1,29 @@ package com.tngtech.archunit.library.adr.markdown; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + public final class ExpectedExampleAdr { + private final String resource; + + public ExpectedExampleAdr(final String resource) { + this.resource = resource; + } + @Override public String toString() { - return "---\n" + - "status: accepted\n" + - "date: YYYY-MM-DD\n" + - "decision-makers: John Doe, Romain Rochegude\n" + - "consulted: John Doe, Romain Rochegude\n" + - "informed: John Doe, Romain Rochegude\n" + - "---\n" + - "\n" + - "# {short title, representative of solved problem and found solution}\n" + - "\n" + - "## Context and Problem Statement\n" + - "\n" + - "{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}\n" + - "\n" + - "## Decision Drivers\n" + - "\n" + - "* {decision driver 1, e.g., a force, facing concern, ...}\n" + - "* {decision driver 2, e.g., a force, facing concern, ...}\n" + - "\n" + - "## Considered Options\n" + - "\n" + - "* {title of option 1}\n" + - "* {title of option 2}\n" + - "* {title of option 3}\n" + - "\n" + - "## Decision Outcome\n" + - "\n" + - "Chosen option: \"{title of option 1}\", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.\n" + - "\n" + - "### Consequences\n" + - "\n" + - "* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}\n" + - "* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}\n" + - "\n" + - "### Confirmation\n" + - "\n" + - "{Describe how the implementation / compliance of the ADR can/will be confirmed. Is there any automated or manual fitness function? If so, list it and explain how it is applied. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}\n" + - "\n" + - "## Pros and Cons of the Options\n" + - "\n" + - "### {title of option 1}\n" + - "\n" + - "{description}\n" + - "\n" + - "{example}\n" + - "\n" + - "* Good, because {argument a}\n" + - "* Good, because {argument b}\n" + - "* Neutral, because {argument c}\n" + - "* Bad, because {argument d}\n" + - "\n" + - "### {title of other option}\n" + - "\n" + - "{description}\n" + - "\n" + - "{example}\n" + - "\n" + - "* Good, because {argument a}\n" + - "* Good, because {argument b}\n" + - "* Neutral, because {argument c}\n" + - "* Bad, because {argument d}\n" + - "\n" + - "## More Information\n" + - "\n" + - "{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}"; + try (final InputStream is = getClass().getClassLoader().getResourceAsStream(this.resource)) { + assert is != null; + return new BufferedReader( + new InputStreamReader(is) + ).lines().collect( + Collectors.joining("\n") + ); + } catch (final IOException e) { + throw new RuntimeException(e); + } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java index 433a06f709..eb146a4290 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/adr/markdown/MdAdrTest.java @@ -10,7 +10,7 @@ void testMdGeneration() { MatcherAssert.assertThat( new ExampleAdr().toString(), new IsEqual<>( - new ExpectedExampleAdr().toString() + new ExpectedExampleAdr("data/expected_example_adr.md").toString() ) ); }