Skip to content

alexbalmus/dci_java_playground

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

An attempt to implement a DCI example in Java

If you are new to Data-Context-Interaction, then it's recommended you read the following article first: https://fulloo.info/Documents/ArtimaDCI.html

DCI is a valuable (but not very well known) software design & architecture approach and OOP paradigm shift.

Some key ideas:

  • two orthogonal perspectives: "what the system is" (Data) and "what the system does" (Interaction in a Context)
  • explicitly capture object interaction as a network of roles played by objects in a context
  • objects should initially be simple / "dumb" / data-objects: they may contain methods related to themselves (for validations, invariants protection etc.) but not for interaction with other objects
  • objects will receive additional behavior from the roles they play in a certain context
  • identity of the role-playing objects must be retained (i.e. a role may not be implemented as a wrapper)

Due to the particular characteristics, it's rather difficult to implement in a strongly typed programming language like Java. Two reference examples have been provided by DCI's authors, one using a library called Qi4J and the other using Java's reflection API: https://fulloo.info/Examples/JavaExamples/

Approach taken here: extension methods provided by Project Lombok: https://projectlombok.org/features/experimental/ExtensionMethod

In this example, "Data" is represented by simple JPA entities of type Account:

Account:

@Entity
@Table(name="account")
public class Account
{
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "balance")
    private Double balance;

    // Constructors, getters and setters omitted for brevity.

    public void increaseBalanceBy(final Double amount)
    {
        balance += amount;
    }

    public void decreaseBalanceBy(final Double amount)
    {
        balance -= amount;
    }
    
    // equals and hashCode omitted for brevity.
}

The following are two roles used in a money transfer scenario: the "source account" role and the "destination account" role:

MoneyTransferContext.Account_Source:

    /**
     * Account_Source role
     * Uses extension method MoneyTransferContext.Account_Destination#receive
     */
    @DciRole
    @ExtensionMethod(MoneyTransferContext.Account_Destination.class)
    static class Account_Source
    {
        @SuppressWarnings("unused")
        public static void transfer(Account thiz, Account destination, Double amount)
        {
            if (thiz.getBalance() < amount)
            {
                throw new BalanceException(INSUFFICIENT_FUNDS); // Rollback.
            }
    
            thiz.decreaseBalanceBy(amount);
    
            destination.receive(amount);
        }
    }

The Lombok annotation @ExtensionMethod specifies that this class will use the extension method defined in Account_Destination.

The custom annotation @DciRole is a marker to better identify DCI roles.

Notice how "destination" gains the new (contextual) extension method called "receive", which is defined below:

MoneyTransferContext.Account_Destination:

    /**
     * Account_Destination role
     */
    @DciRole
    static class Account_Destination
    {
        @SuppressWarnings("unused")
        public static void receive(Account thiz, Double amount)
        {
            thiz.increaseBalanceBy(amount);
        }
    }

The context gathers the objects participating in the use case and calls the necessary role methods:

MoneyTransferContext:

/**
 * Money transfer DCI context.
 * Uses extension method MoneyTransferContext.Account_Source#transfer
 */
@DciContext
@ExtensionMethod(MoneyTransferContext.Account_Source.class)
public class MoneyTransferContext
{
    public static final String INSUFFICIENT_FUNDS = "Insufficient funds.";

    /**
     * DCI context (use case): transfer amount from source account to destination account
     */
    public void executeSourceToDestinationTransfer(
        final Double amountToTransfer,
        final Account source,
        final Account destination)
    {
        source.transfer(destination, amountToTransfer);
    }
    
    // Other code omitted for brevity.
}

The Lombok annotation @ExtensionMethod specifies that this class will use the extension method defined in Account_Source.

The custom annotation @DciContext is a marker to better identify DCI Contexts. For convenience, it's also shaped as a custom stereotype for Spring's @Component.

Notice how "source" gains the new (contextual) extension method called "transfer".

Other approaches I've tried:

DCI candidates (not necessarily compliant):

Non-DCI, but still aiming to capture some of the valuable ideas from it:

More info:

https://fulloo.info/

https://fulloo.info/Documents/ArtimaDCI.html

https://en.wikipedia.org/wiki/Data,_context_and_interaction

https://blog.encodeart.dev/series/dci-typescript-tutorial

About

A DCI-inspired Approach For Java

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages