Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

4Developers 2015 - Using Domain-Driven Design in Legacy Systems

No description
by

Piotr Wyczesany

on 9 June 2015

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of 4Developers 2015 - Using Domain-Driven Design in Legacy Systems

There is
only

one thing
that You have to do
to apply Domain-Driven Design

in Legacy System:

Change
Your Mindset

Problem with Developers
You are going to write a System
web based
highly scalable
in any technology
that will let users do blah blah blah
Domain
Oh... and another one
Setters and getters FTW!
public class
Money {

private
BigDecimal amount;

private
Currency currency;


public void
setAmount(BigDecimal amount) {

this
.amount = amount;
}


public
BigDecimal getAmount() {

return
amount;
}


public void
setCurrency(Currency currency) {

this
.currency = currency;
}


public
Currency getCurrency() {

return
currency;
}
}
No encapsulation
No explicit intent
No business rules
No behavior
Problem with Legacy
It was (and still is) written by Developers with Problems
"Doing the same thing over and over again
and expecting different results."

- Albert Einstein
Software Archeology
-
@j_palka
Most projects are brownfield
Adapt
Refactor
Rewrite
Yes, we can
Problem with
Domain-Driven Design
"We say that effectively applying
tactical techniques
of Domain-Driven Design (DDD)
requires a clean, Bounded Context."
- Eric Evans
On the other hand
Domain-Driven Design
Aggregates
Entities
Value Objects
Repositories
Domain Services
Policies
First things first
"Not all the system
will be well designed"

- Eric Evans
Strategic Design
Sales
Finance
Reporting
Core
Learning
DDDesign
Choose Your enemy
Don't start without clear boundary
Find small and interesting problem
that can be fixed with DDD
Find motivated people
(even cherry-pick them)
Main Tool
Anti-Corruption Layer
Layers revisited
DAO
Business Logic
UI
Model ?
a.k.a. No layers
4 Strategies
Bubble Context
When
(Bubble Context)
Low commitment to DDD or unexperienced team

You cannot change existing database schema

Limited range of data required from Legacy System

You need rapid synchronization with Legacy System
How
(Bubble Context)
ACL as repository implementation
Benefits
Drawbacks
Safe space to learn DDD principles

Does not commit You (nor Your organization) very much
(most of the system is untouched)

Chance to evolve it into something bigger

Your Enterprise Architect doesn't have to know
what you are doing :)
Bubble Context is completely dependent
on Parent Context

You are still mapping to existing Legacy schema

Tempting to corrupt Model in Bubble with Legacy Model

Possible performance issues with mapping
(Bubble Context)
(Bubble Context)
Synchronizing
When
(Synchronizing ACL)
Organization committed to major new development with new design approach

You have your own database (or part of it)

You don't need rapid synchronization
How
ACL as asynchronous copying batch process
Benefits
Drawbacks
You are leading your design

Fully standalone in development

Possibly another source code repository

A lot of existing features still working (reports, etc)
Some copying process required

You have to decide what to synchronize and when

Possible a lot of data duplication

No single source of truth
ACL
ACL as Assets
When
(ACL as Assets)
Organization committed to major new development with new design approach

You don't want to reproduce existing functionality (especially from Supporting or Generic Subdomain)

You have own database

You need rapid synchronization with Legacy System
How
ACL as Domain Service implementation
Benefits
Drawbacks
Legacy starts too look like an Asset
(You don't have to rewrite all these features)

You may move really fast

Fully standalone in development (with mock)

Possibly another source code repository
May not be as fast database join

You have to write dumb ACL Service mock
Event Stream
When
(Event Stream)
Organization committed to major new development with new design approach

System integrated through Event Channel

Modest translation of Events from Legacy System

Your Enterprise Architect is OK with that
How
Published Language for Event definitions
Benefits
Drawbacks
Fully decoupled System

Communication only through Events
Fully decoupled System ;)

Legacy System has to be modified
to produce (or consume) Events

Strong infrastructure support
(Synchronizing ACL)
(Synchronizing ACL)
(Synchronizing ACL)
(ACL as Assets)
(ACL as Assets)
(ACL as Assets)
(Event Stream)
(Event Stream)
(Event Stream)
Spring + Hibernate!
let's try Play Framework...
... with Akka!
old and ugly
new and
shiny
package
com.acme.old.and.ugly.entity;

public class
ProjectEntity {

private
Long id;

private
String name;

...
// setters and getters
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

interface
ProjectEntityDao {
ProjectEntity findById( Long id );

void
saveOrUpdate( ProjectEntity projectEntity );
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

public class
HibernateProjectEntityDao
extends
BaseDao
implements
ProjectEntityDao {
...
}
package
com.acme.new.and.shiny.model;

public class
Project {

private final
ProjectNumber id;

private
ProjectName name;


public
Project( ProjectNumber id, ProjectName name ) {

this
.id = id;

this
.name = name;
}


public void
rename( String name ) {

this
.name = ProjectName.fromString( name );
}


public
String fullName() {

return
name.fullName( id );
}
}
package
com.acme.new.and.shiny.model;

interface
ProjectRepository {
Project load( ProjectNumber projectNumber );

void
add( Project project );
}
package
com.acme.new.and.shiny.model;

public final class
ProjectNumber {

public final
Long value;


public
ProjectNumber( Long value ) {

this
.value = value;
}


public
String toString() {

return
value.toString();
}
...
// hashCode & equals
}
package
com.acme.new.and.shiny.acl;

import
com.acme.new.and.shiny.model.*;
import
com.acme.old.and.ugly.dao.*

public class
AclProjectRepository
implements
ProjectRepository {

private
ProjectEntityDao projectEntityDao;

private
AclProjectTransformer aclProjectTransformer;


public
Project load( ProjectNumber projectNumber ) {

return
aclProjectTransformer.transform(
projectEntityDao.findById( projectNumber.value ));
}
...
}
Model
package
com.acme.new.and.shiny.model;
ProjectNumber.java
Project.java
ProjectRepository.java
Anti-Corruption Layer
package
com.acme.new.and.shiny.acl;

import
com.acme.new.and.shiny.model.*;
import
com.acme.old.and.ugly.entity.*;

class
AclProjectTransformer {

public
Project transform( ProjectEntity projectEntity ) {

return new
Project(

new
ProjectNumber( projectEntity.getId() ),
ProjectName.fromString( projectEntity.getName() ));
}
}
AclProjectRepository.java
AclProjectTransformer.java
package
com.acme.new.and.shiny.acl;
Legacy code
ProjectEntity.java
package
com.acme.old.and.ugly.entity;
package
com.acme.old.and.ugly.dao;
HibernateProjectEntityDao.java
ProjectEntityDao.java
package
com.acme.new.and.shiny.model;
import static
org.apache.commons.lang.StringUtils.*;

public class
ProjectName {

public final
String name;


private
ProjectName( String name ) {

this
.name = name;
}


public static
ProjectName fromString( String name ) {

if
isBlank( name )
throw new
IllegalArgumentException(
"Project name cannot be blank"
);

return new
ProjectName(name);
}


public
String fullName( ProjectNumber projectNumber ) {

return

"["
+ projectNumber +
"] "
+ name;
}

...
// hashCode & equals
}
ProjectName.java
old and ugly
package
com.acme.old.and.ugly.entity;

public class
ProjectEntity {

private
Long id;

private
String name;

...
// setters and getters
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

interface
ProjectEntityDao {
ProjectEntity findById( Long id );

void
saveOrUpdate( ProjectEntity projectEntity );
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

public class
HibernateProjectEntityDao
extends
BaseDao
implements
ProjectEntityDao {
...
}
Legacy Code!
ProjectEntity.java
package
com.acme.old.and.ugly.entity;
package
com.acme.old.and.ugly.dao;
HibernateProjectEntityDao.java
ProjectEntityDao.java
new and
shiny
package
com.acme.new.and.shiny.model;

public class
Project {

private final
ProjectNumber id;

private
ProjectName name;


public
Project(ProjectNumber id, ProjectName name) {

this
.id = id;

this
.name = name;
}


public void
rename(String name) {

this
.name = ProjectName.fromString(name);
}


public
String fullName() {

return
name.fullName(id);
}
}
package
com.acme.new.and.shiny.model;

interface
ProjectRepository {
Project load(ProjectNumber projectNumber);

void
add(Project project);
}
package
com.acme.new.and.shiny.model;

public final class
ProjectNumber {

public final
Long value;


public
ProjectNumber(Long value) {

this
.value = value;
}


public
String toString() {

return
value.toString();
}
}
package
com.acme.new.and.shiny.acl;

import
....

public class
AclProjectSynchronization
implements
Runnable {

private
ProjectEntityDao projectEntityDao;

private
MongoProjectRepository projectRepository;

private
AclProjectTransformer aclProjectTransformer;


private
DateTime lastSynchronizationTime;


public void
run() {

for
( ProjectEntity projectEntity : projectEntityDao.findCreatedAfter( lastSynchronizationTime ) ) {
projectRepository.add( aclProjectTransformer.transform( projetEntity ) );
}

for
( Project project : projectRepository.findUpdatedAfter( lastSynchronizationTime ) ) {
projectEntityDao.findById( project.id.value ).setName( project.name() )
}

lastSynchronizationTime =
new
DateTime();
}
}
Model
package
com.acme.new.and.shiny.model;
ProjectNumber.java
Project.java
ProjectRepository.java
Anti-Corruption Layer
package
com.acme.new.and.shiny.acl;

import
com.acme.new.and.shiny.model.*;
import
com.acme.old.and.ugly.entity.*;

class
AclProjectTransformer {

public
Project transform( ProjectEntity projectEntity ) {

return new
Project(

new
ProjectNumber( projectEntity.getId() ),
ProjectName.fromString( projectEntity.getName() ));
}
}
AclProjectSynchronization.java
AclProjectTransformer.java
package
com.acme.new.and.shiny.acl;
package
com.acme.new.and.shiny.model;
import static
org.apache.commons.lang.StringUtils.*;

public class
ProjectName {

public final
String name;


private
ProjectName(String name) {

this
.name = name;
}


public static
ProjectName fromString(String name) {

if
isBlank(name)
throw new
IllegalArgumentException(
"Project name cannot be blank"
);

return new
ProjectName(name);
}


public
String fullName(ProjectNumber projectNumber) {

return

"["
+ projectNumber +
"] "
+ name;
}
}
ProjectName.java
package
com.acme.new.and.shiny.infrastucture.mongo;

import
com.acme.new.and.shiny.model.*;
import
java.util.*;
import
org.joda.time.*;

public class
MongoProjectRepository
implements
ProjectRepository {


public
Project load( ProjectNumber projectNumber ) { ... }

public void
add( Project project ) { ... }


public
Collection<Project> findUpdatedAfter( DateTime time ) { ... }
}
Infrastructure
MongoProjectRepository.java
package
com.acme.new.and.shiny.infrastructure.mongo;
old and ugly
package
com.acme.old.and.ugly.entity;

public class
ProjectEntity {

private
Long id;

private
String name;

private
CustomerEntity customer;

...
// setters and getters
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

interface
ProjectEntityDao {
ProjectEntity findById( Long id );

void
saveOrUpdate( ProjectEntity projectEntity );
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

public class
HibernateProjectEntityDao
extends
BaseDao
implements
ProjectEntityDao {
...
}
Legacy Code!
ProjectEntity.java
package
com.acme.old.and.ugly.entity;
package
com.acme.old.and.ugly.dao;
HibernateProjectEntityDao.java
ProjectEntityDao.java
new and
shiny
package
com.acme.new.and.shiny.model;

public class
Project {

private final
ProjectNumber id;

private
ProjectName name;

private
Customer customer;


public
Project( ProjectNumber id, ProjectName name, Customer customer ) {
...

this
.customer = customer;
}


public
String fullName( CustomerService customerService ) {

return
name.fullName( id, customerService.customerName( customer ) );
}
...
}
package
com.acme.new.and.shiny.model;

interface
ProjectRepository {
Project load(ProjectNumber projectNumber);

void
add(Project project);
}
package
com.acme.new.and.shiny.model;

public final class
ProjectNumber {

public final
Long value;


public
ProjectNumber(Long value) {

this
.value = value;
}


public
String toString() {

return
value.toString();
}
}
package
com.acme.new.and.shiny.acl;

import
....

public class
AclCustomerNameService
implements
CustomerNameService {

private
CustomerEntityDao customerEntityDao;


public
CustomerName customerName( Customer customer ) {
CustomerEntity customerEntity = customerEntityDao.findById( customer.id );

return new
CustomerName( customerEntity.getName() );
}
}
Model
package
com.acme.new.and.shiny.model;
ProjectNumber.java
Project.java
ProjectRepository.java
Anti-Corruption Layer
AclCustomerNameService.java
package
com.acme.new.and.shiny.acl;
package
com.acme.new.and.shiny.model;
import static
org.apache.commons.lang.StringUtils.*;

public class
ProjectName {

public final
String name;

...


public
String fullName( ProjectNumber projectNumber, CustomerName customerName ) {

return

"["
+ projectNumber +
"]("
+ customerName +
") "
+ name;
}
}
ProjectName.java
package
com.acme.old.and.ugly.entity;

public class
CustomerEntity {

private
Long id;

private
String name;

private
Collection<ProjectEntity> projects;

...
// setters and getters
}
CustomerEntity.java
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.CustomerEntity;

interface
CustomerEntityDao {
CustomerEntity findById( Long id );

void
saveOrUpdate( CustomerEntity customerEntity );
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.CustomerEntity;

public class
HibernateCustomerEntityDao
extends
BaseDao
implements
CustomerEntityDao {
...
}
HibernateCustomerEntityDao.java
CustomerEntityDao.java
package
com.acme.new.and.shiny.model;

public final class
CustomerName {

public final
String name;


public
CustomerName( String name ) {

this
.name = name;
}


public
String toString() {

return
name.toString();
}
...
// hashCode & equals
}
CustomerName.java
package
com.acme.new.and.shiny.model;

public interface
CustomerNameService {
CustomerName customerName( Customer customer );
}
CustomerNameService.java
package
com.acme.new.and.shiny.model;

public final class
Customer {

public final
Long id;


public
Customer( Long id ) {

this
.id = id;
}


public
String toString() {

return
id.toString();
}
...
// hashCode & equals
}
Customer.java
old and ugly
package
com.acme.old.and.ugly.entity;

public class
ProjectEntity {

private
Long id;

private
String name;

...
// setters and getters
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

interface
ProjectEntityDao {
ProjectEntity findById(Long id);

void
saveOrUpdate(ProjectEntity projectEntity);
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.ProjectEntity;

public class
HibernateProjectEntityDao
extends
BaseDao
implements
ProjectEntityDao {
...
}
Legacy Code!
ProjectEntity.java
package
com.acme.old.and.ugly.entity;
package
com.acme.old.and.ugly.dao;
HibernateProjectEntityDao.java
ProjectEntityDao.java
new and
shiny
package
com.acme.new.and.shiny.model;

public class
Project {

private final
ProjectNumber id;

private
ProjectName name;

private
Customer customer;


public
Project(ProjectNumber id, ProjectName name, Customer customer) {
...

this
.customer = customer;
}


public void
rename(String name) {

this
.name = ProjectName.fromString(name);
}


public
String fullName(CustomerService customerService) {

return
name.fullName(id, customerService.customerName(customer));
}
}
package
com.acme.new.and.shiny.model;

interface
ProjectRepository {
Project load(ProjectNumber projectNumber);

void
add(Project project);
}
package
com.acme.new.and.shiny.model;

public final class
ProjectNumber {

public final
Long value;


public
ProjectNumber(Long value) {

this
.value = value;
}


public
String toString() {

return
value.toString();
}
}
package
com.acme.new.and.shiny.acl;

import
....

public class
AclCustomerNameService
implements
CustomerNameService {

private
CustomerEntityDao customerEntityDao;


public
CustomerName customerName(Customer customer) {
CustomerEntity customerEntity = customerEntityDao.findById(customer.id);

return new
CustomerName(customerEntity.getName());
}
}
Model
package
com.acme.new.and.shiny.model;
ProjectNumber.java
Project.java
ProjectRepository.java
Anti-Corruption Layer
AclCustomerNameService.java
package
com.acme.new.and.shiny.acl;
package
com.acme.new.and.shiny.model;
import static
org.apache.commons.lang.StringUtils.*;

public class
ProjectName {

public final
String name;


private
ProjectName(String name) {

this
.name = name;
}


public static
ProjectName fromString(String name) {

if
isBlank(name)
throw new
IllegalArgumentException(
"Project name cannot be blank"
);

return new
ProjectName(name);
}


public
String fullName(ProjectNumber projectNumber, CustomerName customerName) {

return

"["
+ projectNumber +
"]("
+ customerName +
") "
+ name;
}
}
ProjectName.java
package
com.acme.old.and.ugly.entity;

public class
CustomerEntity {

private
Long id;

private
String name;

...
// setters and getters
}
CustomerEntity.java
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.CustomerEntity;

interface
CustomerEntityDao {
CustomerEntity findById(Long id);

void
saveOrUpdate(CustomerEntity customerEntity);
}
package
com.acme.old.and.ugly.dao;

import
com.acme.old.and.ugly.entity.CustomerEntity;

public class
HibernateCustomerEntityDao
extends
BaseDao
implements
CustomerEntityDao {
...
}
HibernateCustomerEntityDao.java
CustomerEntityDao.java
package
com.acme.new.and.shiny.model;

public final class
CustomerName {

public final
String name;


public
CustomerName(String name) {

this
.name = name;
}


public
String toString() {

return
name.toString();
}
}
CustomerName.java
package
com.acme.new.and.shiny.model;

public interface
CustomerNameService {
CustomerName customerName(Customer customer);
}
CustomerNameService.java
package
com.acme.new.and.shiny.model;

public final class
Customer {

public final
Long id;


public
Customer(Long id) {

this
.id = id;
}


public
String toString() {

return
id.toString();
}
}
Customer.java
Legacy Events
ProjectCreatedEvent.java
package
com.acme.old.and.ugly.published.language;

public class
ProjectCreatedEvent {

public final
Long projectId;

public final
Long customerId;

public final
String name;


public
ProjectCreatedEvent(
Long projectId, Long customerId, String name) {

this
.projectId = projectId;

this
.customerId = customerId;

this
.name = name;
}
}
package
com.acme.old.and.ugly.published.language;
Event Bus
AclProjectCreatedEventHandler.java
package
com.acme.new.and.shiny.acl;

import
com.acme.old.and.ugly.published.language.*;
import
com.acme.new.and.shiny.model.*;
import
com.acme.new.and.shiny.infrastructure.events.EventHandler;

public class
ProjectCreatedEventHandler
implements
EventHandler<ProjectCreatedEvent> {

private
ProjectRepository projectRepository;


public void
handle( ProjectCreatedEvent event ) {
projectRepository.add(
new
Project(

new
ProjectNumber( event.projectId ),
ProjectName.fromString( event.name ),

new
Customer( event.customerId )));
}
}
"good long term software
is not commonly about reuse,

its about the ability to rewrite completely isolated bits
without hitting the big bang"
- Greg Young
Strategic Design
Ports & Adapters
a.k.a. Hexagon
Model
UI
Infrastructure
ACL
...
...
...
[ Value Object ]
[ Value Object ]
[ Side-effect free function ]
[ Aggregate root ]
[ Repository ]
[ Value Object ]
[ Value Object ]
[ Side-effect
free function ]
[ Aggregate root ]
[ Repository ]
[ Repository ]
[ Value Object ]
[ Value Object ]
[ Domain Service ]
[ Value Object ]
[ Value Object ]
[ Aggregate root ]
[ Domain Event ]
Legacy Service
ProjectService.java
package
com.acme.old.and.ugly.logic;

public class
ProjectEntityServiceImpl
implements
ProjectEntityService {

private
ProjectEntityDao projectEntityDao;

private
EventBus eventBus;

// this is Legacy - of course business logic is done higher, in UI! ;)

public
saveOrUpdate( ProjectEntity projectEntity,
boolean
newEntity ) {
projectEntityDao.saveOrUpdate(projectEntity);

if
(newEntity) {
// my fav ;)
...
// other fancy stuff for new ProjectEntity
eventBus.raise(
new
ProjectCreatedEvent(...) );
}
}
}
package
com.acme.old.and.ugly.logic;
[ Repository ]
[ Value Object ]
[ Value Object ]
[ Domain Service ]
[ Value Object ]
[ Value Object ]
[ Aggregate root ]
ACL as Repository implementation
ACL as asynchronous
copying batch process
ACL as Domain Service implementation
Published Language for Event definitions
-
@pwyczes

Using Domain-Driven Design
in Legacy Systems

@pwyczes
www.eventuallyinconsistent.com
meetup.com/DDD-KRK
by Piotr Wyczesany
Thank you!
Investment
Benefits
Problems, problems, problems...
Developers
Learning
Legacy
Wrong boxes
we cannot into packages
com
acme
old
and
ugly
dao
entity
service
manager
OOP?
Anyone?
Arrogance
I should read that book...
this time till the end!
"I already know all that sh*t"
but do we?
1.
2.
3.
Repeat it, as a mantra!
( works both ways )
Example
1. We can create Project with Project Number
and not-blank Project Name
2. We can rename Project
3. We can obtain Project's Full Name
which should look like this:
"
[ <Project Number> ] <Project Name>
"
and for Customer
"
[ <Project Number> ] (<Customer Name>) <Project Name>
"
Full transcript