Re: [axonframework] Manupilating EventSourced member in an aggregate's collection fails

1,009 views
Skip to first unread message

Allard Buijze

unread,
Feb 16, 2013, 7:47:53 AM2/16/13
to axonfr...@googlegroups.com
Hi Viswanath,

by default, the Fixture tries to verify whether you've applied event sourcing correctly. For example, it can detect when you have done a state change inside the command handler method. Under some circumstances, it may return false positives (reporting an error when everything is ok). The comparison is done by comparing the values of each objects fields with an Event Sourced instance of your aggregate. However, if two objects implement an equals method or the Comparable interface, the outcome of the equals or compareTo method is used. If that method returns false (or a value != 0), the test fails.

Since you're storing fields in a Map, which does override equals, Axon uses the equals method on the map. In turn, the Map will use the equals method on the AttributeInformation object to compare equality.

Does your AttributeInformation class implement the equals method? If so, then it probably returns false where it shouldn't, OR, you've really made an event sourcing mistake somewhere. In that case, check if you don't accidentally change a field in a method that is not an @EventHandler.

You can switch off the check altogether by using fixture.setReportIllegalStateChange(false);

Cheers,

Allard

On Thu, Feb 14, 2013 at 4:33 PM, Viswanath Jayachandran <visw...@innovation-district.com> wrote:
Hello there,

I have an aggregate with EventSourcedMember as follows

public class PersonAggregateRoot extends AbstractAnnotatedAggregateRoot<String>
{
      @AggregateIdentifier
       private String identifier;
    
       @EventSourcedMember
        private Map<String, AttributeInformation> attributeInformationMap = new HashMap<>();

        // constructors, getters, setters and other methods are present
}


The AttributeInformation is as follows

public class AttributeInformation extends AbstractAnnotatedEntity {

  private String verificationCode;
  private Long verificationCodeIssuedAt;
  private Short numberOfFailedAttempts = 0;
 
  // constructors, getters, setters and other methods are present
}


Now, I have an event handler in the aggregate "PersonAggregateRoot" as below

@EventHandler
   public void onAttributeVerificationFailedEvent(final AttributeVerificationFailedEvent event) {

    Assert.notNull(event);

    String profileAttributeId = event.getProfileAttributeId();

    log.debug("Profile attribute with ID: {} was NOT successfully verified for person with ID:{} ",
        profileAttributeId, event.getIdentifier());

    if (attributeInformationMap.containsKey(profileAttributeId)) {
      AttributeInformation attributeInformation = attributeInformationMap.get(profileAttributeId);
      attributeInformation.resetVerificationCode();
      attributeInformation.resetVerificationCodeIssuedAt();
      attributeInformation.incrementNumberOfFailedAttempts();
    }
  }


I noticed when I try to manipulate entries in the map, the event handler isn't happy.  If the content of the method above was just

attributeInformationMap.remove(event.getProfileAttributeId());

My unit test passes without any exception.  The content of the test class method is as below

@Test
  public void should_fail_verify_profile_attribute_phone()
  {
    assertNotNull(profileAttributesMap);
    assertFalse(profileAttributesMap.isEmpty());

    assertNotNull(phoneProfileAttributeId);
    assertTrue(StringUtils.isNotBlank(phoneProfileAttributeId));

    fixture
        .given(new PersonCreatedEvent(PERSON_IDENTIFIER, IDENTITY, IDP_ID),
            new PersonEnrichedEvent(PERSON_IDENTIFIER, profileAttributesMap),
            new VerificationCodeCreatedEvent(PERSON_IDENTIFIER,phoneProfileAttributeId,VERIFICATION_CODE))
        .when(new VerifyAttributeCommand(PERSON_IDENTIFIER, phoneProfileAttributeId, "WRONG-VERIFICATION-CODE",
            BasicProfileAttributes.PHONE_NUMBER))
        .expectEvents(new AttributeVerificationFailedEvent(PERSON_IDENTIFIER, phoneProfileAttributeId));

  }


when executed, it fails with the following message

org.axonframework.test.AxonAssertionError: Illegal state change detected! Property "com.innovation_district.onegini.domain.person.PersonAggregateRoot.attributeInformationMap" has different value when sourcing events.
Working aggregate value:     <{4010659f-2c26-481f-94af-4070f9e20297=com.innovation_district.onegini.domain.person.AttributeInformation@7a774652[verificationCode=<null>,verificationCodeIssuedAt=<null>,numberOfFailedAttempts=1,aggregateRoot=Person -- identifier:personId]}>
Value after applying events: <{4010659f-2c26-481f-94af-4070f9e20297=com.innovation_district.onegini.domain.person.AttributeInformation@2ca9f04e[verificationCode=<null>,verificationCodeIssuedAt=<null>,numberOfFailedAttempts=1,aggregateRoot=Person -- identifier:personId]}>
    at com.innovation_district.onegini.command.person.VerifyAttributeCommandTest.should_fail_verify_profile_attribute_phone(VerifyAttributeCommandTest.java:45)


Please provide me some guidance as to what I am doing wrong here. As you can see, the values of both these events are the same

Thanks a lot in advance for your time and efforts!

--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Viswanath Jayachandran

unread,
Feb 18, 2013, 2:30:46 AM2/18/13
to axonfr...@googlegroups.com
Hi Allard,

Thanks a lot for taking time to reply.

I didn't change the state of the EventSourcedMember of my aggregate root anywhere else but in the event handler. I just didn't implement hashCode() and equals() methods initially.

However, I went implemented them later as below and everything worked just fine :)

  @Override
  public int hashCode() {
    return new HashCodeBuilder().append(this.verificationCode).append(this.verificationCodeIssuedAt).append
        (this.numberOfFailedAttempts).toHashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    final AttributeVerificationInformation other = (AttributeVerificationInformation) obj;
    return new EqualsBuilder().append(this.verificationCode, other.verificationCode)
        .append(this.verificationCodeIssuedAt, other.verificationCodeIssuedAt)
        .append(this.numberOfFailedAttempts, other.numberOfFailedAttempts).isEquals();
  }


Thank you once again for helping me to fix the problem.

Best regards
Viswanath Jayachandran

Allard Buijze

unread,
Feb 18, 2013, 2:43:42 AM2/18/13
to axonfr...@googlegroups.com
Hi Viswanath,

something not Axon related, but your hashCode method looks wrong. The hash code should never be based on mutable data. The hashCode method should always return the same value for the same instance. Failure to do so may cause the object to become *lost* in HashMap (when used as key), HashSet and other hash based collections.

Cheers,

Allard
Reply all
Reply to author
Forward
0 new messages