From 5801269815e0fbbdb12956b026c5e83c3b403ccd Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Mon, 19 Oct 2026 22:34:32 +0200 Subject: [PATCH 1/2] Add notResponsibleFor to BoundedContext (#372) Allows to state explicitly what a Bounded Context is not responsible for, symmetric to the existing responsibilities attribute. The Merge Bounded Contexts refactoring merges the lists and drops entries that are a responsibility of the merged context. --- .../merge-bounded-contexts-test-12-input.cml | 9 +++++ .../dsl/BoundedContextDSLParsingTest.xtend | 37 +++++++++++++++++++ .../refactoring/MergeBoundedContextsTest.java | 18 +++++++++ .../contextmapper/dsl/ContextMappingDSL.xtext | 1 + .../ContextMappingDSLFormatter.xtend | 1 + .../MergeBoundedContextsRefactoring.java | 10 +++++ 6 files changed, 76 insertions(+) create mode 100644 org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml diff --git a/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml new file mode 100644 index 00000000..ce276dfa --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml @@ -0,0 +1,9 @@ +BoundedContext TicketSales { + responsibilities = "Selling fare products", "Refunds" + notResponsibleFor = "Gate validation", "Support cases" +} + +BoundedContext CustomerCare { + responsibilities = "Support cases" + notResponsibleFor = "Payment processing", "Gate validation" +} diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend index 7ec9b6ca..099d33b9 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend @@ -258,6 +258,43 @@ class BoundedContextDSLParsingTest { String.format(ONLY_TEAMS_CAN_REALIZE_OTHER_BOUNDED_CONTEXT, "NotATeam")); } + @Test + def void canDefineWhatContextIsNotResponsibleFor() { + // given + val String dslSnippet = ''' + BoundedContext testContext { + responsibilities = "Selling fare products", "Refunds" + notResponsibleFor = "Gate validation", "Support cases" + } + '''; + // when + val ContextMappingModel result = parseHelper.parse(dslSnippet); + // then + assertThatNoParsingErrorsOccurred(result); + assertThatNoValidationErrorsOccurred(result); + val notResponsibleFor = result.boundedContexts.get(0).notResponsibleFor; + assertEquals(2, notResponsibleFor.size); + assertEquals("Gate validation", notResponsibleFor.get(0)); + assertEquals("Support cases", notResponsibleFor.get(1)); + } + + @Test + def void canDefineNotResponsibleForWithoutEqualSign() { + // given + val String dslSnippet = ''' + BoundedContext testContext { + notResponsibleFor "Gate validation" + } + '''; + // when + val ContextMappingModel result = parseHelper.parse(dslSnippet); + // then + assertThatNoParsingErrorsOccurred(result); + assertThatNoValidationErrorsOccurred(result); + assertEquals(1, result.boundedContexts.get(0).notResponsibleFor.size); + assertEquals("Gate validation", result.boundedContexts.get(0).notResponsibleFor.get(0)); + } + @Test def void canDefineAttributesWithoutEqualSign() { // given diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java index 4716b608..4b214af2 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java @@ -196,6 +196,24 @@ void canMergeImplementationTechnology() throws IOException { assertEquals("Java, Scala", bc.getImplementationTechnology()); } + @Test + void canMergeNotResponsibleFor() throws IOException { + // given + String inputModelName = "merge-bounded-contexts-test-12-input.cml"; + CMLResource input = getResourceCopyOfTestCML(inputModelName); + MergeBoundedContextsRefactoring refactoring = new MergeBoundedContextsRefactoring("TicketSales", "CustomerCare"); + + // when + refactoring.refactor(input); + + // then + ContextMappingModel model = input.getContextMappingModel(); + assertEquals(1, model.getBoundedContexts().size()); + BoundedContext bc = model.getBoundedContexts().get(0); + assertEquals(3, bc.getResponsibilities().size()); + assertEquals(List.of("Gate validation", "Payment processing"), bc.getNotResponsibleFor()); + } + @Test void canMergeEmptyImplementationTechnologies() throws IOException { // given diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext b/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext index 28786cb6..a2b8f90e 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext @@ -58,6 +58,7 @@ BoundedContext: (('domainVisionStatement' ('=')? domainVisionStatement=STRING)? & ('type' ('=')? type=BoundedContextType)? & (('responsibilities' ('=')? responsibilities+=STRING) ("," responsibilities+=STRING)*)? & + (('notResponsibleFor' ('=')? notResponsibleFor+=STRING) ("," notResponsibleFor+=STRING)*)? & ('implementationTechnology' ('=')? implementationTechnology=STRING)? & ('knowledgeLevel' ('=')? knowledgeLevel=KnowledgeLevel)?) ('businessModel' ('=')? businessModel=STRING)? & diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend b/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend index b7265896..f2187c44 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend @@ -120,6 +120,7 @@ class ContextMappingDSLFormatter extends TacticDDDLanguageFormatter { boundedContext.regionFor.keyword("domainVisionStatement").prepend[newLine] boundedContext.regionFor.keyword("type").prepend[newLine] boundedContext.regionFor.keyword("responsibilities").prepend[newLine] + boundedContext.regionFor.keyword("notResponsibleFor").prepend[newLine] boundedContext.regionFor.keyword("implementationTechnology").prepend[newLine] boundedContext.regionFor.keyword("knowledgeLevel").prepend[newLine] diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java index d3fe1617..038f80b9 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java @@ -77,6 +77,7 @@ protected void doRefactor() { if (bc1.getType().equals(BoundedContextType.TEAM)) bc1.getRealizedBoundedContexts().addAll(bc2.getRealizedBoundedContexts()); bc1.getResponsibilities().addAll(bc2.getResponsibilities()); + mergeNotResponsibleFor(bc1, bc2); bc1.setImplementationTechnology(mergeImplementationTechnologies(bc1.getImplementationTechnology(), bc2.getImplementationTechnology())); // remove BC2 @@ -91,6 +92,15 @@ protected void doRefactor() { bc2Model.setTopComment("// Due to the application of 'Merge Bounded Contexts' this file no longer contains any Bounded Contexts."); } + private void mergeNotResponsibleFor(BoundedContext bc1, BoundedContext bc2) { + for (String notResponsibleFor : bc2.getNotResponsibleFor()) { + if (!bc1.getNotResponsibleFor().contains(notResponsibleFor)) + bc1.getNotResponsibleFor().add(notResponsibleFor); + } + // the merged context is responsible for everything one of the two contexts was responsible for + bc1.getNotResponsibleFor().removeAll(bc1.getResponsibilities()); + } + private String mergeImplementationTechnologies(String implementationTechnology1, String implementationTechnology2) { if (implementationTechnology1 == null && implementationTechnology2 == null) return null; From 3d31b17932c16dbd0ed9a627af414318c1bce12a Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Tue, 20 Oct 2026 10:54:05 +0200 Subject: [PATCH 2/2] Rename notResponsibleFor to nonResponsibilities (#372) Uses a noun like the existing responsibilities attribute and the other Bounded Context attributes, so both lists read consistently. --- .../merge-bounded-contexts-test-12-input.cml | 4 ++-- .../dsl/BoundedContextDSLParsingTest.xtend | 20 +++++++++---------- .../refactoring/MergeBoundedContextsTest.java | 4 ++-- .../contextmapper/dsl/ContextMappingDSL.xtext | 2 +- .../ContextMappingDSLFormatter.xtend | 2 +- .../MergeBoundedContextsRefactoring.java | 12 +++++------ 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml index ce276dfa..09670211 100644 --- a/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml +++ b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-bounded-contexts-test-12-input.cml @@ -1,9 +1,9 @@ BoundedContext TicketSales { responsibilities = "Selling fare products", "Refunds" - notResponsibleFor = "Gate validation", "Support cases" + nonResponsibilities = "Gate validation", "Support cases" } BoundedContext CustomerCare { responsibilities = "Support cases" - notResponsibleFor = "Payment processing", "Gate validation" + nonResponsibilities = "Payment processing", "Gate validation" } diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend index 099d33b9..0c00b5e0 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/BoundedContextDSLParsingTest.xtend @@ -259,12 +259,12 @@ class BoundedContextDSLParsingTest { } @Test - def void canDefineWhatContextIsNotResponsibleFor() { + def void canDefineNonResponsibilities() { // given val String dslSnippet = ''' BoundedContext testContext { responsibilities = "Selling fare products", "Refunds" - notResponsibleFor = "Gate validation", "Support cases" + nonResponsibilities = "Gate validation", "Support cases" } '''; // when @@ -272,18 +272,18 @@ class BoundedContextDSLParsingTest { // then assertThatNoParsingErrorsOccurred(result); assertThatNoValidationErrorsOccurred(result); - val notResponsibleFor = result.boundedContexts.get(0).notResponsibleFor; - assertEquals(2, notResponsibleFor.size); - assertEquals("Gate validation", notResponsibleFor.get(0)); - assertEquals("Support cases", notResponsibleFor.get(1)); + val nonResponsibilities = result.boundedContexts.get(0).nonResponsibilities; + assertEquals(2, nonResponsibilities.size); + assertEquals("Gate validation", nonResponsibilities.get(0)); + assertEquals("Support cases", nonResponsibilities.get(1)); } @Test - def void canDefineNotResponsibleForWithoutEqualSign() { + def void canDefineNonResponsibilitiesWithoutEqualSign() { // given val String dslSnippet = ''' BoundedContext testContext { - notResponsibleFor "Gate validation" + nonResponsibilities "Gate validation" } '''; // when @@ -291,8 +291,8 @@ class BoundedContextDSLParsingTest { // then assertThatNoParsingErrorsOccurred(result); assertThatNoValidationErrorsOccurred(result); - assertEquals(1, result.boundedContexts.get(0).notResponsibleFor.size); - assertEquals("Gate validation", result.boundedContexts.get(0).notResponsibleFor.get(0)); + assertEquals(1, result.boundedContexts.get(0).nonResponsibilities.size); + assertEquals("Gate validation", result.boundedContexts.get(0).nonResponsibilities.get(0)); } @Test diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java index 4b214af2..5ffefd5e 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsTest.java @@ -197,7 +197,7 @@ void canMergeImplementationTechnology() throws IOException { } @Test - void canMergeNotResponsibleFor() throws IOException { + void canMergeNonResponsibilities() throws IOException { // given String inputModelName = "merge-bounded-contexts-test-12-input.cml"; CMLResource input = getResourceCopyOfTestCML(inputModelName); @@ -211,7 +211,7 @@ void canMergeNotResponsibleFor() throws IOException { assertEquals(1, model.getBoundedContexts().size()); BoundedContext bc = model.getBoundedContexts().get(0); assertEquals(3, bc.getResponsibilities().size()); - assertEquals(List.of("Gate validation", "Payment processing"), bc.getNotResponsibleFor()); + assertEquals(List.of("Gate validation", "Payment processing"), bc.getNonResponsibilities()); } @Test diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext b/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext index a2b8f90e..0ad4f58a 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext @@ -58,7 +58,7 @@ BoundedContext: (('domainVisionStatement' ('=')? domainVisionStatement=STRING)? & ('type' ('=')? type=BoundedContextType)? & (('responsibilities' ('=')? responsibilities+=STRING) ("," responsibilities+=STRING)*)? & - (('notResponsibleFor' ('=')? notResponsibleFor+=STRING) ("," notResponsibleFor+=STRING)*)? & + (('nonResponsibilities' ('=')? nonResponsibilities+=STRING) ("," nonResponsibilities+=STRING)*)? & ('implementationTechnology' ('=')? implementationTechnology=STRING)? & ('knowledgeLevel' ('=')? knowledgeLevel=KnowledgeLevel)?) ('businessModel' ('=')? businessModel=STRING)? & diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend b/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend index f2187c44..20ca810d 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend @@ -120,7 +120,7 @@ class ContextMappingDSLFormatter extends TacticDDDLanguageFormatter { boundedContext.regionFor.keyword("domainVisionStatement").prepend[newLine] boundedContext.regionFor.keyword("type").prepend[newLine] boundedContext.regionFor.keyword("responsibilities").prepend[newLine] - boundedContext.regionFor.keyword("notResponsibleFor").prepend[newLine] + boundedContext.regionFor.keyword("nonResponsibilities").prepend[newLine] boundedContext.regionFor.keyword("implementationTechnology").prepend[newLine] boundedContext.regionFor.keyword("knowledgeLevel").prepend[newLine] diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java index 038f80b9..ab98347a 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeBoundedContextsRefactoring.java @@ -77,7 +77,7 @@ protected void doRefactor() { if (bc1.getType().equals(BoundedContextType.TEAM)) bc1.getRealizedBoundedContexts().addAll(bc2.getRealizedBoundedContexts()); bc1.getResponsibilities().addAll(bc2.getResponsibilities()); - mergeNotResponsibleFor(bc1, bc2); + mergeNonResponsibilities(bc1, bc2); bc1.setImplementationTechnology(mergeImplementationTechnologies(bc1.getImplementationTechnology(), bc2.getImplementationTechnology())); // remove BC2 @@ -92,13 +92,13 @@ protected void doRefactor() { bc2Model.setTopComment("// Due to the application of 'Merge Bounded Contexts' this file no longer contains any Bounded Contexts."); } - private void mergeNotResponsibleFor(BoundedContext bc1, BoundedContext bc2) { - for (String notResponsibleFor : bc2.getNotResponsibleFor()) { - if (!bc1.getNotResponsibleFor().contains(notResponsibleFor)) - bc1.getNotResponsibleFor().add(notResponsibleFor); + private void mergeNonResponsibilities(BoundedContext bc1, BoundedContext bc2) { + for (String nonResponsibility : bc2.getNonResponsibilities()) { + if (!bc1.getNonResponsibilities().contains(nonResponsibility)) + bc1.getNonResponsibilities().add(nonResponsibility); } // the merged context is responsible for everything one of the two contexts was responsible for - bc1.getNotResponsibleFor().removeAll(bc1.getResponsibilities()); + bc1.getNonResponsibilities().removeAll(bc1.getResponsibilities()); } private String mergeImplementationTechnologies(String implementationTechnology1, String implementationTechnology2) {