Skip to content

Cleanup Java#1

Open
wilvdb wants to merge 15 commits intoagilepartner:masterfrom
wilvdb:master
Open

Cleanup Java#1
wilvdb wants to merge 15 commits intoagilepartner:masterfrom
wilvdb:master

Conversation

@wilvdb
Copy link
Copy Markdown

@wilvdb wilvdb commented May 10, 2020

Hello,

I just cleanup Java (remove singleton, add getter/setter when needed, and rework generic interfaces).
I just checked how to integrate GraphQL, but I don't know what is the role of InventoryItemService.

@cpontet
Copy link
Copy Markdown
Member

cpontet commented May 20, 2020

I am not forgetting you. I just haven't had time to review your work yet. I'm gonna try to spend some time during the long weekend.

private void apply(InventoryItemCheckedIn evt) {
this.stock += evt.quantity;
@Override
protected <T extends Event> void apply(T event) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is exactly what we want to avoid by having the "ugly" reflection code in the AggregateRoot base class.

I want to avoid the if-then-else like the plague and I'd much rather stay with nice, clean, and small apply methods that respect the Single Responsibility Principle instead.

I agree that reflection is not the most efficient and clean way to implement that. There are cleaner ways to implement the AggregateRoot base class to avoid reflection, using a more functional approach for example. But this code is for a workshop and I personally prefer focussing on a clean domain than the plumbing code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this point, I'm totally agree it is not ideal. I'm just looking for a better solution.
I propose to implement it as bellow:

public abstract class AggregateRoot {
    protected Map<Class<? extends Event>, Consumer<? extends Event>> eventsConsumer = new HashMap<>();

    protected AggregateRoot(UUID id) {
        registerEventsConsumer();
        this.id = id;
    }

    protected abstract void registerEventsConsumer();
}

class InventoryItem extends AggregateRoot {
@Override
    protected void registerEventsConsumer() {
        eventsConsumer.put(InventoryItemCreated.class, (Consumer<InventoryItemCreated>) this::apply);
        eventsConsumer.put(InventoryItemRenamed.class, (Consumer<InventoryItemRenamed>) this::apply);
        eventsConsumer.put(InventoryItemCheckedIn.class, (Consumer<InventoryItemCheckedIn>) this::apply);
        eventsConsumer.put(InventoryItemCheckedOut.class, (Consumer<InventoryItemCheckedOut>) this::apply);
        eventsConsumer.put(InventoryItemDeactivated.class, (Consumer<InventoryItemDeactivated>) this::apply);
    }

private void apply(InventoryItemCreated event) {
        this.name = event.getName();
        this.stock = event.getQuantity();
        this.active = true;
    }
}

Single Responsibility Principle is respected, and there is no more ugly code.

@Override
public <T extends Command> void register(CommandHandler<T> handler, Class<?> cmdClass) {
map.put(cmdClass.getSimpleName(), handler);
public <T extends Command> void register(CommandHandler<T> handler, Class<T> cmdClass) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I got confused with generics in Java. Not used to them.

@SuppressWarnings("unused")
private void apply(NameChanged evt) {
name = evt.name;
@Override
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here. You lose expressiveness of your domain code just because you want to make your base class simpler. I wouldn't to that, and instead, find a way to make the base class cleaner without impairing the clean domain.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for InventoryItem

Comment thread Step07/app/build.gradle Outdated
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok:1.18.12'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this code is a workshop, and because not everyone is familiar with lombok, I wanted to use vanilla java code and not carry extra dependencies doing black magic. I admit though that lombok is a good library.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used it when I started to modify the code, but it has been removed since.

import java.util.UUID;

@Getter
@Setter(AccessLevel.PROTECTED)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouuuuhhh black magic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Black magic has been removed.


import java.util.UUID;

@Getter
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More black magic

@Getter
@Setter
public abstract class Event implements Message {
public UUID aggregateId;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand it seems counter-intuitive to have public fields, but most likely the events will be serialized, and there is no real need for encapsulation here, since events (and command for that matter) are mainly data containers, and have no logic.
Therefore, I go for simplicity and don't need getters and setters that just add syntactic noise here... using the black magic of lombok

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it's counter-intuitive. And in case field are not encapsulated, the temptation is use something like lombok.

Repository<InventoryItem> repository = new InMemoryRepository<InventoryItem>();

CommandResolver resolver = InMemoryCommandResolver.getInstance();
CommandResolver resolver = new InMemoryCommandResolver();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Why not.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this change because Spring ensure that each bean it manages, is a singleton.
But in this case, I think it will be used in actor modeling in the next steps. So, it make sense to keep it as a singleton.


private void applyChange(Event event, boolean isNew) {
invokeApplyIfEntitySupports(event);
apply(event);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same stuff than before here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants