diff --git a/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-aggregates-test-8-input.cml b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-aggregates-test-8-input.cml new file mode 100644 index 00000000..877aaa0e --- /dev/null +++ b/org.contextmapper.dsl.tests/integ-test-files/refactorings/merge-aggregates-test-8-input.cml @@ -0,0 +1,17 @@ +BoundedContext TicketSales { + Aggregate Tickets { + Entity Ticket { + aggregateRoot + String zone + } + Invariant RefundsNeverExceedPrice "The sum of refunds never exceeds the price paid" + } + + Aggregate Refunds { + Entity Refund { + aggregateRoot + long amountInCents + } + Invariant "A refund amount is always positive" + } +} diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/AggregateDSLParsingTest.xtend b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/AggregateDSLParsingTest.xtend index 4af9af30..3633ebbd 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/AggregateDSLParsingTest.xtend +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/AggregateDSLParsingTest.xtend @@ -82,6 +82,35 @@ class AggregateDSLParsingTest { result.boundedContexts.get(0).aggregates.get(0).responsibilities.get(0)); } + @Test + def void canDefineAggregateInvariants() { + // given + val String dslSnippet = ''' + BoundedContext testContext { + Aggregate Tickets { + Entity Ticket { + aggregateRoot + String zone + } + Invariant "The sum of refunds never exceeds the price paid" + Invariant ValidatedTicketsAreNotRefundable "A validated ticket cannot be refunded" + } + } + '''; + // when + val ContextMappingModel result = parseHelper.parse(dslSnippet); + // then + assertThatNoParsingErrorsOccurred(result); + assertThatNoValidationErrorsOccurred(result); + val aggregate = result.boundedContexts.get(0).aggregates.get(0); + assertEquals(1, aggregate.domainObjects.size); + assertEquals(2, aggregate.invariants.size); + assertNull(aggregate.invariants.get(0).name); + assertEquals("The sum of refunds never exceeds the price paid", aggregate.invariants.get(0).description); + assertEquals("ValidatedTicketsAreNotRefundable", aggregate.invariants.get(1).name); + assertEquals("A validated ticket cannot be refunded", aggregate.invariants.get(1).description); + } + @Test def void canDefineAggregateKnowledgeLevel() { // given diff --git a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeAggregatesTest.java b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeAggregatesTest.java index 54d53011..ef504266 100644 --- a/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeAggregatesTest.java +++ b/org.contextmapper.dsl.tests/src/org/contextmapper/dsl/refactoring/MergeAggregatesTest.java @@ -54,6 +54,24 @@ void canMergeAggregates() throws IOException { assertTrue(domainObjectNames.contains("Address")); } + @Test + void canMergeInvariants() throws IOException { + // given + String inputModelName = "merge-aggregates-test-8-input.cml"; + CMLResource input = getResourceCopyOfTestCML(inputModelName); + MergeAggregatesRefactoring refactoring = new MergeAggregatesRefactoring("Tickets", "Refunds"); + + // when + refactoring.refactor(input); + + // then + Aggregate agg = input.getContextMappingModel().getBoundedContexts().get(0).getAggregates().get(0); + assertEquals("Tickets", agg.getName()); + assertEquals(2, agg.getInvariants().size()); + assertEquals("RefundsNeverExceedPrice", agg.getInvariants().get(0).getName()); + assertEquals("A refund amount is always positive", agg.getInvariants().get(1).getDescription()); + } + @Test void canMergeAggregatesInModule() 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 0ad4f58a..e3e2b8c4 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/ContextMappingDSL.xtext @@ -196,10 +196,15 @@ Aggregate : ((services+=Service) | (resources+=Resource) | (consumers+=Consumer) | - (domainObjects+=SimpleDomainObject))* + (domainObjects+=SimpleDomainObject) | + (invariants+=Invariant))* CLOSE)? ; +Invariant: + 'Invariant' (name=ID)? description=STRING +; + Application: "Application" (name=ID)? (OPEN ((commands+=CommandEvent)* & 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 20ca810d..1ce258c7 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/formatting2/ContextMappingDSLFormatter.xtend @@ -327,6 +327,9 @@ class ContextMappingDSLFormatter extends TacticDDDLanguageFormatter { for (domainObject : aggregate.domainObjects) { domainObject.format } + for (invariant : aggregate.invariants) { + invariant.regionFor.keyword('Invariant').prepend[newLine] + } for (service : aggregate.services) { service.format diff --git a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeAggregatesRefactoring.java b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeAggregatesRefactoring.java index 38986d6e..0967d14a 100644 --- a/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeAggregatesRefactoring.java +++ b/org.contextmapper.dsl/src/org/contextmapper/dsl/refactoring/MergeAggregatesRefactoring.java @@ -84,6 +84,7 @@ protected void doRefactor() { addElementsToEList(agg1.getResponsibilities(), agg2.getResponsibilities()); addElementsToEList(agg1.getServices(), agg2.getServices()); addElementsToEList(agg1.getUserRequirements(), agg2.getUserRequirements()); + addElementsToEList(agg1.getInvariants(), agg2.getInvariants()); // update context maps for (ContextMap contextMap : getAllContextMaps()) {