Vous êtes sur la page 1sur 240

UNIVERSIDADE TCNICA DE LISBOA

INSTITUTO SUPERIOR TCNICO

Development of Rich Domain Models


with Atomic Actions
Joo Manuel Pinheiro Cachopo
(Mestre)

Dissertao para obteno do Grau de Doutor em


Engenharia Informtica e de Computadores

Orientador:
Co-Orientador:

Doutor Antnio Manuel Ferreira Rito da Silva


Doutor Joo Emlio Segurado Pavo Martins

Jri:
Presidente:
Vogais:

Reitor da Universidade Tcnica de Lisboa


Doutor Jos Luiz Fiadeiro
Doutor Maurice Herlihy
Doutor Amlcar dos Santos Costa Sernadas
Doutor Joo Emlio Segurado Pavo Martins
Doutor Antnio Manuel Ferreira Rito da Silva
Doutor Antnio Paulo Teles de Menezes Correia Leito
Doutor Rodrigo Seromenho Miragaia Rodrigues

Julho de 2007

Resumo
O modelo de domnio o elemento central de uma aplicao com objectos moderna.
l que reside o conhecimento que a aplicao tem sobre o domnio do problema. Logo,
medida que se desenvolvem aplicaes para problemas com domnios maiores e mais
complexos, os modelos de domnio tornam-se mais ricos. Infelizmente, a implementao de modelos de domnio ricos utilizando as linguagens de programao com objectos
actuais uma tarefa difcil.
Para simplificar esta tarefa, eu proponho estender as linguagens de programao com
novos construtores que permitam a utilizao de aces atmicas, a especificao da estrutura do modelo de domnio, e a implementao de regras de consistncia. Estes novos
construtores so introduzidos na linguagem Java de uma forma amigvel para o programador, de modo a que os programadores possam us-los sem alteraes significativas no
processo de desenvolvimento.
Para implementar aces atmicas, proponho um novo modelo de Memria Transaccional em Software, e descrevo uma implementao deste modelo em Javaa JVSTM.
Depois, proponho uma nova linguagema Domain Modeling Languageque permite a
especificao das entidades e das relaes existentes num domnio. Finalmente, proponho a utilizao de Predicados de Consistncia que tiram partido da existncia de aces
atmicas para permitir a implementao de regras de consistncia de forma ortogonal
implementao do restante comportamento.

Abstract
The domain model is the central element of a modern object-oriented application. It embodies the applications knowledge about the problem domain. Therefore, as applications
encompass problems with larger and more complex domains, domain models become
both larger and richer than ever. Yet, implementing a rich domain model with current
object-oriented programming languages is a difficult task.
To simplify this task, I propose to extend object-oriented programming languages with
new constructs that allow the use of atomic actions, the specification of a domain models
structure, and the implementation of domain consistency rules. I introduce these new
constructs in the Java programming language in a programmer-friendly way, so that
programmers may use them without major changes in the development process.
To support atomic actions, I propose a new model of Software Transactional Memory
and describe an implementation of this model as a pure Java librarythe JVSTM. Then,
I propose a new languagethe Domain Modeling Languageto allow the specification of
the entities and the relationships between entities of a domain. Finally, I propose the
use of Consistency Predicates that build on the support for atomic actions to allow the
implementation of consistency rules orthogonally to the implementation of the remaining
behavior.

Palavras-chave
Programao Concorrente com Objectos
Memria Transaccional em Software
Programas de Domnios Complexos
Modelao de Domnio
Predicados de Consistncia
Arquitecturas de Software

Keywords
Concurrent Object-Oriented Programming
Software Transactional Memory
Domain-Intensive Applications
Domain Modeling
Consistency Predicates
Software Architectures

Acknowledgments
No man is an island. . . is a famous quotation from John Donne, and it is so for a
good reason. We all have others with whom we work, with whom we talk, with whom we
despair, with whom we laugh, and with whom we dream. All of these people, one way or
another, influence what we do and who we are.
The work leading to this dissertation was a long journey that would not have been
possible without the help of many. To thank them all, I have written the most inspired
acknowledgments of all time, which, unfortunately, the margins of these pages are too
small to contain. So, instead, I shall give a much paler alternative that does not do justice
to all the help that I got.
First and foremost, I would like to thank Professor Antnio Rito da Silva, my adviser
and my mentor, who went much beyond the call of duty in his advising work. The completion of this work owes much to his enthusiastic encouragement, continuous guidance,
and permanent availability. Moreover, any coherence that you may find in this dissertation is a direct consequence of his unique ability to rise up above the trees and see the
forest.
I would like to thank, also, Professor Joo Pavo Martins, my co-adviser, both for
having initiated me into the arts of scientific research, and for his support throughout my
work.
I am grateful to INESC-ID, and specially to its Software Engineering Group, for providing me not only the means to do my work, but also an healthy and enjoyable environment
to work in. I am deeply indebted, also, to all the ESWs members, and, in particular, to
my colleague and friend Antnio Leito, for all the numerous discussions that helped me
to shed light into the more obscure parts of this work. I owe to Antnio, also, much of my
knowledge on the art of programming and on programming languages.
This work would not have been possible without the support of CIIST, and, specially,
of the Fnix team. I am grateful to them for their enthusiasm in applying my work to the
Fnix system. I would never had started this work in the first place if Fnix did not exist.
I am specially grateful to Eng. Lus Cruz for all his support and insightful discussions
during my work. It was a pleasure to work with him.

To my parents Marcos and Olinda, to my sister Anabela, to my parents-in-law Anbal


and Isabel, and to Susana and Paulo I thank all the encouragement and support.
Finally, regardless of all the remaining help, none of this work would have happened
without the enduring support, encouragement, and sacrifices of my lovingly wife Ana and
my dearest son Rui. I heart-feelingly thank them for all their patience and ability to lead
me through the most difficult phases of my work. I thank Ana, also, for her analytical
reasoning that helped me countless times during my work. Discussing the most difficult
problems with her allowed me, time and again, to make things clearer in my head.

Julho de 2007
Joo Manuel Pinheiro Cachopo

Contents

1 Introduction

1.1 Domain-Intensive Applications . . . . . . . . . . . . . . . . . . . . . . . . .

1.2 Evolution Rather than Revolution: an Engineering Approach

. . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.2.2 Guiding Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.2.1 General Approach

1.3 Thesis Statement

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.5 Outline of the Dissertation . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.4 Notation

2 Motivation, Problem Statement, and Approach


2.1 Domain Models in the Software Development Process

11
. . . . . . . . . . . .

12

2.1.1 Software Development as the Transformation of Artifacts . . . . . . .

12

2.1.2 Domain Model: A Central Artifact in the Development Process . . . .

13

2.1.3 Domain Model at Various Levels: Analysis, Design, Implementation .

14

2.1.4 Standard Architecture for a Domain-Intensive Application . . . . . .

15

2.1.5 The Domain Layer Dependence on the Infrastructural Layer . . . . .

17

2.2 Example of an Application in the Banking Domain . . . . . . . . . . . . . .

18

2.2.1 Examples Rationale . . . . . . . . . . . . . . . . . . . . . . . . . . .

18

2.2.2 Applications Functionality . . . . . . . . . . . . . . . . . . . . . . . .

19

2.2.3 Basic Domain Modeling Terminology . . . . . . . . . . . . . . . . . .

20

ii

CONTENTS

2.2.4 Initial Design of the Banking Domain Model . . . . . . . . . . . . . .


2.3 Problem Statement

22

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23

2.4 Two Approaches to Solve the Problem . . . . . . . . . . . . . . . . . . . . .

26

2.4.1 The MDA approach . . . . . . . . . . . . . . . . . . . . . . . . . . . .

26

2.4.2 This Dissertations Approach: Reduce the Gap between Languages

27

2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

3 The Difficulties of Implementing a Domain Model

31

3.1 Implementation of a Concurrent Domain Model . . . . . . . . . . . . . . . .

31

3.1.1 Basic Thread-Safety . . . . . . . . . . . . . . . . . . . . . . . . . . .

32

3.1.2 Thread-Safety with More than One Object . . . . . . . . . . . . . . .

35

3.2 Failure Recovery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

39

3.3 Implementation of the Banking Domain Models Structure . . . . . . . . . .

42

3.3.1 Implementation of Classes . . . . . . . . . . . . . . . . . . . . . . . .

43

3.3.2 Implementation of Associations . . . . . . . . . . . . . . . . . . . . .

46

3.4 Implementation of the Banking Domain Models Behavior . . . . . . . . . .

50

3.4.1 Implementation of the Basic Deposit and Withdraw Operations

. . .

51

. . . . . . . . . .

52

3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

54

3.4.2 Implementation of the Clients Total Balance Limit

4 Versioned Software Transactional Memory

57

4.1 Introduction to Software Transactional Memory . . . . . . . . . . . . . . . .

58

4.1.1 Atomic Actions and the Property of Atomicity . . . . . . . . . . . . . .

58

4.1.2 Read Sets, Write Sets, Commits, and Aborts . . . . . . . . . . . . . .

59

4.1.3 Transaction Linearizability . . . . . . . . . . . . . . . . . . . . . . . .

60

4.2 The Rationale for the Versioned STM . . . . . . . . . . . . . . . . . . . . . .

61

4.3 The Versioned STM Model . . . . . . . . . . . . . . . . . . . . . . . . . . . .

62

4.3.1 Model Elements and Terminology . . . . . . . . . . . . . . . . . . . .

62

CONTENTS

iii

4.3.2 Operations on Versioned Boxes . . . . . . . . . . . . . . . . . . . . .

64

4.3.3 The Transactions Life-Cycle . . . . . . . . . . . . . . . . . . . . . . .

65

4.3.4 The Linearizability of Top-Level Transactions . . . . . . . . . . . . . .

67

4.3.5 Garbage Collection of Old Values . . . . . . . . . . . . . . . . . . . .

70

4.3.6 Examples: The Bank Revisited

71

. . . . . . . . . . . . . . . . . . . . .

4.4 Implementation of the Versioned STM Model

. . . . . . . . . . . . . . . . .

73

4.4.1 The JVSTMs API . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

74

4.4.2 Interaction with the Java Memory Model . . . . . . . . . . . . . . . .

79

4.4.3 Implementation of Versioned Boxes . . . . . . . . . . . . . . . . . . .

80

4.4.4 Implementation of Transactions . . . . . . . . . . . . . . . . . . . . .

82

4.4.5 Atomic Commits . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

85

4.4.6 Speculative Read-Only Transactions . . . . . . . . . . . . . . . . . .

87

4.4.7 Implementation of Garbage Collection

. . . . . . . . . . . . . . . . .

88

4.5 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

95

4.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

97

5 Domain Modeling Language

99

5.1 DMLs Rationale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100


5.2 Grammar Notation and Lexical Structure . . . . . . . . . . . . . . . . . . . 102
5.3 Domain Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.4 Value Types

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

5.5 Entity Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106


5.5.1 Syntax of Entity Type Declarations . . . . . . . . . . . . . . . . . . . 106
5.5.2 Semantics of Entity Type Declarations . . . . . . . . . . . . . . . . . 108
5.6 Associations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.6.1 Syntax of Association Declarations . . . . . . . . . . . . . . . . . . . 112
5.6.2 Semantics of Association Declarations . . . . . . . . . . . . . . . . . 114

iv

CONTENTS

5.6.2.1 Roles with a multiplicity upper-bound of one . . . . . . . . . 116


5.6.2.2 Roles with a multiplicity upper-bound greater than one . . . 117
5.6.2.3 Bidirectional associations

. . . . . . . . . . . . . . . . . . . 119

5.6.2.4 Sets returned by the method getRoleSet . . . . . . . . . . 120


5.6.2.5 Association objects and their listeners . . . . . . . . . . . . . 120
5.7 Implementation of a Domain Specification . . . . . . . . . . . . . . . . . . . 122
5.7.1 Using the JVSTM to Make a Transactional Domain . . . . . . . . . . 123
5.7.2 Implementing Entity Types

. . . . . . . . . . . . . . . . . . . . . . . 124

5.7.3 Implementing Associations

. . . . . . . . . . . . . . . . . . . . . . . 125

5.7.3.1 Storing the Associations Links . . . . . . . . . . . . . . . . . 126


5.7.3.2 Implementing the Role Methods . . . . . . . . . . . . . . . . 127
5.7.3.3 Implementing the Association-Aware Set . . . . . . . . . . . 128
5.7.3.4 Implementing the Association Class . . . . . . . . . . . . . . 130
5.7.3.5 Implementing Different Role Types . . . . . . . . . . . . . . . 130
5.7.3.6 Enforcing Multiplicity Constraints . . . . . . . . . . . . . . . 134
5.8 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
5.8.1 Associations as First-Class Language Constructs . . . . . . . . . . . 135
5.8.2 Patterns for Implementing Associations . . . . . . . . . . . . . . . . . 136
5.8.3 Generating the Code for Associations . . . . . . . . . . . . . . . . . . 137
5.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

6 Consistency Predicates

141

6.1 Domain Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142


6.1.1 Consistency of Single Objects . . . . . . . . . . . . . . . . . . . . . . 142
6.1.2 Consistency of Rich Domain Models
6.2 Examples of Constraints

. . . . . . . . . . . . . . . . . . 144

. . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

6.3 Consistency Predicates for Atomic Actions . . . . . . . . . . . . . . . . . . . 149

CONTENTS

6.4 Consistency Predicates in Java . . . . . . . . . . . . . . . . . . . . . . . . . 152


6.5 Enforcement of Multiplicities with Consistency Predicates . . . . . . . . . . 154
6.6 Implementation of Consistency Predicates in the JVSTM . . . . . . . . . . . 155
6.7 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
6.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

7 Validation

163

7.1 The Fnix Case Study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164


7.1.1 Fnix History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
7.1.2 The Original Fnix Software Architecture . . . . . . . . . . . . . . . . 165
7.1.3 The Use of this Dissertations Work in the Development of the Fnix
System

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

7.1.3.1 Implementation of the Fnix Domain Model with the DML . . 166
7.1.3.2 Other Benefits of Using the DML . . . . . . . . . . . . . . . . 168
7.1.3.3 The JVSTM in the Fnix System . . . . . . . . . . . . . . . . 169
7.1.4 The Fnix Transactional Workload

. . . . . . . . . . . . . . . . . . . 171

7.1.4.1 Total Number of Transactions Over Time . . . . . . . . . . . 172


7.1.4.2 The Read/Write and Write/Conflicts Ratios . . . . . . . . . . 175
7.1.4.3 The Dimension of the Transactions . . . . . . . . . . . . . . 177
7.2 JVSTM Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
7.2.1 Benchmark Running Environment . . . . . . . . . . . . . . . . . . . 179
7.2.2 Results for the DSTM2 Benchmarks

. . . . . . . . . . . . . . . . . . 180

7.2.2.1 Results for the List Benchmark

. . . . . . . . . . . . . . . . 181

7.2.2.2 Results for the Red-Black Tree Benchmark . . . . . . . . . . 185


7.2.2.3 Results for the Skip List Benchmark
7.2.3 Results for the STMBench7 Benchmark

. . . . . . . . . . . . . 188

. . . . . . . . . . . . . . . . 192

7.2.3.1 The STMBench7 Data Structure . . . . . . . . . . . . . . . . 192

vi

CONTENTS

7.2.3.2 The STMBench7 Operations . . . . . . . . . . . . . . . . . . 193


7.2.3.3 The STMBench7 Execution Parameters and Results . . . . . 193
7.2.3.4 Experimental Setup . . . . . . . . . . . . . . . . . . . . . . . 194
7.2.3.5 Throughput Results . . . . . . . . . . . . . . . . . . . . . . . 194
7.2.3.6 Latency Results . . . . . . . . . . . . . . . . . . . . . . . . . 200
7.3 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201

8 Conclusions

203

8.1 Main Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203


8.2 Future Research . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205

Bibliography

207

List of Figures

1.1 The continuous space from data-intensive to domain-intensive applications.

1.2 Graphical notation used to illustrate the concurrent execution of methods.

2.1 The layered architecture from [Fowler, 2002]. . . . . . . . . . . . . . . . . .

16

2.2 The layered architecture from [Evans, 2003]. . . . . . . . . . . . . . . . . .

17

2.3 First design for the banking domain model. . . . . . . . . . . . . . . . . . .

22

2.4 Second design for the banking domain model. . . . . . . . . . . . . . . . . .

24

3.1 The possible execution of two concurrent calls to the method deposit for
the same Account instance. . . . . . . . . . . . . . . . . . . . . . . . . . .

33

3.2 The possible execution of two concurrent calls to the method deposit for
the same Account instance. . . . . . . . . . . . . . . . . . . . . . . . . . .

34

3.3 The UML class diagram with the relationship between the class Bank and the
class Account: A bank can have many accounts, but an account belongs
to exactly one bank. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

3.4 Execution of the method transfer during the execution of the method

totalBalance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

3.5 Execution of the method transfer during the execution of the method

totalBalance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

3.6 Deadlock caused by the concurrent execution of two calls to the method

transfer, which synchronizes on the source and target accounts.


3.7 Failure during the deposit of a transfer operation.

. . . .

38

. . . . . . . . . . . . . .

39

3.8 Implementation-level class diagram for part of the banking applications domain model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

44

viii

LIST OF FIGURES

4.1 Graphical representation of a versioned box.

. . . . . . . . . . . . . . . . .

63

4.2 The graphical notation used to represent transactions. . . . . . . . . . . . .

68

4.3 Example of 8 successfully committed top-level transactions. . . . . . . . . .

69

4.4 Example of unreachable values.

70

. . . . . . . . . . . . . . . . . . . . . . . .

4.5 Parallel deposits on the same account using the STM model based on versioned boxes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

71

4.6 Execution of the method transfer during the execution of the method

totalBalance using the STM model based on versioned boxes. . . . . . .

72

4.7 Structure that represents a versioned box with three values in its history. .

81

4.8 Values stored in the Transactions fields readMap and writeMap. . . .

84

4.9 Final result after transaction T2 commits. . . . . . . . . . . . . . . . . . . .

85

4.10 Several write transactions committing at the same time. . . . . . . . . . . .

86

4.11 The hierarchy of transactions implemented by the JVSTM. . . . . . . . . . .

88

5.1 The effect that the DML has on the tool chain typically used for Java application development. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.2 The result of compiling an entity type in DML.

. . . . . . . . . . . . . . . . 109

5.3 The result of compiling an hierarchy of entity types in DML. . . . . . . . . . 110


5.4 Methods that a DML compiler must generate for a role declaration with a
multiplicity upper-bound of one. . . . . . . . . . . . . . . . . . . . . . . . . 117
5.5 Methods that a DML compiler must generate for a role declaration with a
multiplicity upper-bound greater than one. . . . . . . . . . . . . . . . . . . 118
5.6 Static fields that a DML compiler must generate to hold the Association
objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
5.7 JVSTM-based implementation of the class ClientState. . . . . . . . . . . 125
5.8 Fields used to store the links of an association. . . . . . . . . . . . . . . . . 126
5.9 Implementation of the methods for a role with a multiplicity upper-bound of
one. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.10 Implementation of the methods for a role with a multiplicity upper-bound
greater than one. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

LIST OF FIGURES

ix

6.1 UML class diagram for two classes with a bidirectional association between
them. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
6.2 UML class diagram showing the central elements for the implementation of
consistency predicates in the JVSTM. . . . . . . . . . . . . . . . . . . . . . 157

7.1 Evolution of the number of classes and associations in the Fnix domain
model.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

7.2 Total transactions successfully processed by the Fnix web application from
October 2006 to June 2007.

. . . . . . . . . . . . . . . . . . . . . . . . . . 172

7.3 Total daily transactions successfully processed by the Fnix web application
on February 2007. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
7.4 Total hourly transactions successfully processed by the Fnix web application on the first day of enrollments, the 17th of February 2007. . . . . . . . 174
7.5 Average daily number of transactions successfully processed by the Fnix
web application for each day of the week from October 2006 to June 2007.

174

7.6 Average hourly number of transactions successfully processed by the Fnix


web application from October 2006 to June 2007.

. . . . . . . . . . . . . . 175

7.7 Monthly total of read transactions, write transactions, and conflicts in the
Fnix web application from October 2006 to June 2007. . . . . . . . . . . . 176
7.8 Hourly average of read transactions, write transactions, and conflicts in the
Fnix web application from October 2006 to June 2007. . . . . . . . . . . . 176
7.9 Hourly total of read transactions, write transactions, and conflicts in the
Fnix web application on the first day of enrollments, the 17th of February
2007. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
7.10 Transactions per second processed by each method for the List Benchmark
with 100% of updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
7.11 Transactions per second processed by each method for the List Benchmark
with 50% of updates.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183

7.12 Transactions per second processed by each method for the List Benchmark
with 10% of updates.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

7.13 Transactions per second processed by each method for the List Benchmark
with 0% of updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

LIST OF FIGURES

7.14 Transactions per second processed by each method for the Red-Black Tree
Benchmark with 100% of updates. . . . . . . . . . . . . . . . . . . . . . . . 186
7.15 Transactions per second processed by each method for the Red-Black Tree
Benchmark with 50% of updates.

. . . . . . . . . . . . . . . . . . . . . . . 187

7.16 Transactions per second processed by each method for the Red-Black Tree
Benchmark with 10% of updates.

. . . . . . . . . . . . . . . . . . . . . . . 187

7.17 Transactions per second processed by each method for the Red-Black Tree
Benchmark with 0% of updates. . . . . . . . . . . . . . . . . . . . . . . . . 188
7.18 Transactions per second processed by each method for the Skip List Benchmark with 100% of updates.

. . . . . . . . . . . . . . . . . . . . . . . . . . 190

7.19 Transactions per second processed by each method for the Skip List Benchmark with 50% of updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
7.20 Transactions per second processed by each method for the Skip List Benchmark with 10% of updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
7.21 Transactions per second processed by each method for the Skip List Benchmark with 0% of updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
7.22 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the long traversals disabled and a readdominated workload.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

7.23 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the long traversals disabled and a read-write
workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
7.24 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the long traversals disabled and a writedominated workload.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

7.25 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the read-write long traversals disabled and
a read-dominated workload.

. . . . . . . . . . . . . . . . . . . . . . . . . . 198

7.26 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the read-write long traversals disabled and
a read-write workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
7.27 Operations per second processed by each synchronization strategy for the
STMBench7 benchmark with all the read-write long traversals disabled and
a write-dominated workload. . . . . . . . . . . . . . . . . . . . . . . . . . . 199

List of Tables

7.1 Composition of the Fnix projects programmers team at IST. . . . . . . . . 165


7.2 Lines of code for different parts of the Fnix system. . . . . . . . . . . . . . 168
7.3 Evolution of the number of classes and associations in the Fnix domain
model.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

7.4 Number of boxes accessed by each type of transaction by the Fnix web
application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
7.5 Number of large transactions, for each type of transaction, in the Fnix web
application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
7.6 The results for the List Benchmark with 100% of updates. . . . . . . . . . . 182
7.7 The results for the List Benchmark with 50% of updates.

. . . . . . . . . . 182

7.8 The results for the List Benchmark with 10% of updates.

. . . . . . . . . . 182

7.9 The results for the List Benchmark with 0% of updates. . . . . . . . . . . . 182
7.10 The results for the Red-Black Tree Benchmark with 100% of updates. . . . 185
7.11 The results for the Red-Black Tree Benchmark with 50% of updates. . . . . 186
7.12 The results for the Red-Black Tree Benchmark with 10% of updates. . . . . 186
7.13 The results for the Red-Black Tree Benchmark with 0% of updates. . . . . . 186
7.14 The results for the Skip List Benchmark with 100% of updates. . . . . . . . 189
7.15 The results for the Skip List Benchmark with 50% of updates. . . . . . . . . 189
7.16 The results for the Skip List Benchmark with 10% of updates. . . . . . . . . 189
7.17 The results for the Skip List Benchmark with 0% of updates. . . . . . . . . 189

xii

LIST OF TABLES

7.18 The results of the STMBench7 benchmark with all the long traversals disabled and a read-dominated workload. . . . . . . . . . . . . . . . . . . . . . 195
7.19 The results of the STMBench7 benchmark with all the long traversals disabled and a read-write workload. . . . . . . . . . . . . . . . . . . . . . . . . 195
7.20 The results of the STMBench7 benchmark with all the long traversals disabled and a write-dominated workload. . . . . . . . . . . . . . . . . . . . . 195
7.21 The results of the STMBench7 benchmark with all the read-write long traversals disabled and a read-dominated workload.

. . . . . . . . . . . . . . . . 197

7.22 The results of the STMBench7 benchmark with all the read-write long traversals disabled and a read-write workload. . . . . . . . . . . . . . . . . . . . . 197
7.23 The results of the STMBench7 benchmark with all the read-write long traversals disabled and a write-dominated workload. . . . . . . . . . . . . . . . . 198
7.24 Maximum latency results for read-only short traversals and short operations
with all the operations enabled and a read-dominated workload.

. . . . . . 200

7.25 Maximum latency results for read-only short traversals and short operations
with all the operations enabled and a read-write workload. . . . . . . . . . . 200
7.26 Maximum latency results for read-only short traversals and short operations
with all the operations enabled and a write-dominated workload. . . . . . . 201

List of Listings
3.1 A non-thread-safe class Account in Java with the basic getBalance,

deposit, and withdraw operations. . . . . . . . . . . . . . . . . . . . . .

32

3.2 The thread-safe version of the class Account. . . . . . . . . . . . . . . . .

33

3.3 The implementation in Java, without any concerns for thread-safety, of the
class Bank with the two methods transfer and totalBalance. . . . . .

35

3.4 An implementation of the class Bank that uses fine-grained locks on the
accounts accessed by each method. . . . . . . . . . . . . . . . . . . . . . .

38

3.5 Reimplementation of the methods withdraw and deposit, for the class

Account, to limit the withdrawal of an amount to the balance of the account,


and to refuse deposits in a closed account. . . . . . . . . . . . . . . . . . .

40

3.6 Reimplementation of the method transfer to verify, before making any


change, that the whole operation will succeed.

. . . . . . . . . . . . . . . .

41

3.7 Reimplementation of the method transfer to undo the withdrawal when


the deposit operation fails. . . . . . . . . . . . . . . . . . . . . . . . . . . .

42

3.8 Partial implementation of the classes Account and ClientAccount. . . .

45

3.9 Implementation of a unidirectional one-to-one association between Bank


and BankAssets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47

3.10 Implementation of a bidirectional one-to-one association between Bank and

BankAssets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49

3.11 Implementation of the unidirectional one-to-many association between the


classes Client and ClientAccount. . . . . . . . . . . . . . . . . . . . .

50

3.12 Generic implementation of the methods deposit and withdraw for the
class Account. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

51

3.13 Implementation of the method convertTo. . . . . . . . . . . . . . . . . . .

52

3.14 Checking the clients total balance before the withdrawal is performed. . . .

53

3.15 Checking the clients total balance after the withdrawal is performed. . . . .

53

4.1 Skeleton of the generic class VBox. . . . . . . . . . . . . . . . . . . . . . . .

74

4.2 Skeleton of the class Transaction. . . . . . . . . . . . . . . . . . . . . . .

75

4.3 Complete implementation of JVSTMs version of the HelloWorld program.

76

4.4 Output produced by the execution of the HelloWorld program. . . . . . . . .

76

4.5 Changes needed in the class Account to use a VBox to hold the Accounts
balance.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

xiv

LIST OF LISTINGS

4.6 Implementation of an atomic version of the method deposit. . . . . . . . .

78

4.7 Use of the annotation Atomic to make the method deposit atomic.

. . .

79

4.8 Structure of the classes VBox and VBoxBody. . . . . . . . . . . . . . . . .

81

4.9 Some of the fields of the class Transaction.

. . . . . . . . . . . . . . . .

82

4.10 The fields of the class ActiveTxRecord. . . . . . . . . . . . . . . . . . . .

91

4.11 The operation used during the start of a new transaction to find the transactions number. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

93

4.12 The operation used when a transaction finishes to clean up unreachable


values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

94

5.1 Syntactic rules for a domain specification. . . . . . . . . . . . . . . . . . . . 104


5.2 Grammar rules for the syntax of a value type declaration. . . . . . . . . . . 105
5.3 Default value types in the DML language. . . . . . . . . . . . . . . . . . . . 106
5.4 Grammar rules for the syntax of an entity type declaration. . . . . . . . . . 107
5.5 Examples of entity type declarations in DML. . . . . . . . . . . . . . . . . . 108
5.6 Grammar rules for the syntax of an association declaration. . . . . . . . . . 113
5.7 Examples of association declarations in DML. . . . . . . . . . . . . . . . . . 114
5.8 The Java generic interfaces Association and AssocListener. . . . . . 121
5.9 Specialization of an association using an AssocListener. . . . . . . . . . 123
5.10 Implementation of the association-aware class AssocSet.

. . . . . . . . . 129

5.11 Implementation of the generic class DirectAssociation. . . . . . . . . . 131


5.12 Implementation of the class InverseAssociation. . . . . . . . . . . . . 132
5.13 The generic interface Role. . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
5.14 The implementation of the class RoleOne. . . . . . . . . . . . . . . . . . . 133
5.15 The implementation of the class RoleMany.

. . . . . . . . . . . . . . . . . 134

6.1 Ensuring that a client has always at least an active checking account. . . . 148
6.2 Implementation of the constraints for closed accounts. . . . . . . . . . . . . 149
6.3 Methods implementing the consistency predicates for the classes A and B. . 151
6.4 Consistency predicate for checking that the client total balance is not negative.153
6.5 Consistency predicate for checking that a client always have a non-closed
checking account. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
6.6 Consistency predicate for checking that a closed ClientAccount has no money.153
6.7 Consistency predicate for checking that a SavingsAccount with no money
must be closed.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

6.8 Consistency predicate for checking that a CheckingAccount is in the list of


accounts to process by the bank if and only if it has a negative balance. . . 154
6.9 Consistency predicates generated by the DML compiler for checking the multiplicity of the AccountOwnership association. . . . . . . . . . . . . . . . 155

Chapter 1

Introduction
The research work that I describe in this dissertation is concerned with the development of
domain-intensive object-oriented applications. In particular, I concentrate on the implementation of the domain model of such applications. Simply put, the main contribution
of this work is the simplification, in a programmers friendly way, of the implementation
of robust and complex domain models.
In this introductory chapter, I start with a characterization of what I call domainintensive applications. Then, I discuss the general approach that guides me through my
research work. These two elements are essential to present my thesis statement, which
I shall do next, followed by the description of how I intend to validate the thesis. Then,
I introduce some basic notation and terminology. Finally, I present the outline of the
dissertation.

1.1

Domain-Intensive Applications

Programmers write computer programs, or applications, to solve particular problems.


Over the years, since the early days of programming, the range of problems solved by
computer applications expanded enormously, both in their scope and in their domains.
Many of the first applications were developed to solve scientific and engineering problems. These applications can be classified as being numeric-intensive: Almost all the
data manipulated by these applications is numeric in nature, or else is naturally represented by numbers. The emphasis in these applications is on the algorithms needed to
manipulate a limited set of numeric data types. One of the first programming languages
FORTRANreflects in its primitives and constructs this emphasis of early programs.
Another area that, shortly after the development of the first computers, benefited
from the power of computer-based problem solving was enterprise data processing. Many

Introduction

Data-intensive
applications
Complexity of domain logic

Domain-intensive
applications
Figure 1.1: The continuous space from data-intensive to domain-intensive applications. We move from data-intensive applications, on the left, to domain-intensive
applications, on the right, as we add more complexity into the domain logic of the
application.

enterprises deal with large volumes of data during their daily operations. Typical examples
of the early days are banks, insurance companies, or any company with many clients,
suppliers, or employees. Unlike the numeric-intensive applications, the first applications
developed for these enterprises do not perform complex numeric computations. Instead,
they store, retrieve, and update the data that represent the various entities relevant to
the enterprises business processes: For instance, the data about each of the enterprises
clients. Because of this emphasis on the storage and retrieval of large volumes of data,
these applications are typically classified as data-intensive. The special needs of dataintensive applications are reflected in the programming language of choice, for many
years, for this kind of applicationsCOBOLand in the later development of database
management systems.
Domain-intensive applications are a natural evolution of data-intensive applications.
Like in data-intensive applications, in domain-intensive applications there are many different types of entities to deal with. In domain-intensive applications, however, the emphasis is put on the domain logic that governs the behavior of and the interactions among
these entities, rather than on the volume of entities processed. Obviously, given this definition, there is no clear-cut separation between data-intensive and domain-intensive
applications. Rather, they form a continuous space of applications, as depicted in Figure 1.1: We move from data-intensive applications to domain-intensive applications as
we add more domain logic into the application.
This trend towards more complex domain logic in the applications evolved alongside
the development of the object-oriented paradigm, which have come to displace COBOL in
the development of new enterprise-grade applications. In fact, object-oriented design and
object-oriented programming languages matured over the last two decades, and are now
the de facto standard for most of the industrial software development.
Yet, even though I firmly believe that object-orientation is the approach that most

1.2 Evolution Rather than Revolution: an Engineering Approach

effectively copes with the complexity of current applications, I argue that current objectoriented programming languages are still limited in how they support the implementation
of rich and complex domain models.
Thus, the central subject of this dissertation is the development of domain-intensive
object-oriented applications. More specifically, I concentrate on the implementation of
one special element of domain-intensive applicationstheir domain model.
The domain model of an application embodies all the knowledge about the domain
of the problem that the application is meant to solve; it plays, therefore, a key role
on a domain-intensive application. The importance of domain models (and thereby the
relevance of this work) is reinforced by the recent surge of interest directed towards the
approach of Domain-Driven Design and Development [Evans, 2003].

1.2

Evolution Rather than Revolution:


an Engineering Approach

In the previous section, I described the context of my research work and justified its
relevance. In this section, I shall describe the general approach that guides my work.
Knowing which approach I followed is important because the approach constrains the
solutions that can be developed. In fact, more than constraining the solutions, the approach that I describe here shapes them. So, by justifying the underlying approach of my
work, I justify the path and many of the options I took in the development of that work.

1.2.1

General Approach

The key idea is that I should strive to propose solutions that are compatible with the
way programmers currently work. The pursuit of this goal is essential, if I intend that a
significant number of programmers1 ever uses the results of this dissertation.
Programmers resist to revolutions, notwithstanding the merit of the newly proposed
approaches. And, in most cases, I agree that their resistance is the most rational approach. Programmers invest years of their lives in education, training, and practice to
become proficient with a given set of knowledge, technologies, and tools. Revolutions
break with most of all this accumulated knowledge, forcing programmers to start it all
over again. Naturally, I believe that revolutions are necessary for the advance of science
and of human knowledge. Yet, good revolutions are hard to find and, when found, are
difficult to bring into common use.
In this dissertation I am not aiming at a revolution in the way of programming com1

Programmers other than myself or my research colleagues.

Introduction

puter applications. Instead, I propose a set of small evolutionary steps on how programming is currently done. I take a more pragmatic stancean engineering approach, if you
willto the problem of improving the current state-of-the-practice on software development.
Likewise, I recognize the value of the enormous amount of effort put into the research
and development of current programming languages, tools, development environments,
and methodologies. So, instead of trying to replace any of these, I intend to leverage on
all of this work by developing solutions that integrate as seamlessly as possible with it.

1.2.2

Guiding Principles

The fundamental tenet of the approach I followed to develop this work is that my proposals should be programmer friendly, in the sense that I should make things easier for
programmers, whilst giving them more powerful constructs. To accomplish this goal, I
established some guiding principles, which I describe next.

Principle 1 (Target Popular Programming Languages)


I should target popular programming languages, even though other languages
might be technically more appropriate.

If I want to reach a wide programmer base, it makes sense to make my contributions


target popular programming languages; programming languages that are commonly used
to develop enterprise applications. This principle has at least one important corollary
that I should not propose radically new languages.
Following this principle, I chose to develop my work in the context of the Java programming language [Arnold, Gosling, and Holmes, 2000; Joy, Guy L. Steele, Gosling,
and Bracha, 2000]. Java is one of the most (if not the most) popular object-oriented
programming language used to develop new enterprise applications. Java is, also, an
excellent example of the evolutionary approach I discussed earlier: One of the factors for
the rapid acceptance of the language by the programming community at large, was the
Java language designers decision to adopt the syntax of C for their new language.
All the examples that I show in this dissertation, as well as the implementation of
my proposals, are in Java. The main results of my work, however, transcend the choice
of a particular programming language. All the examples presented and implementations
developed can be trivially adapted for any other modern object-oriented programming
language that supports concepts similar to Javas.2
2

As a matter of fact, although not described in this dissertation, part of this work was also implemented
in ANSI Common Lisp [ANSI and ITIC, 1996].

1.2 Evolution Rather than Revolution: an Engineering Approach


Principle 2 (Let Programmers Use Their Tools)
I should not interfere with the tools that programmers use, neither by forcing
them to change to other tools, nor by rendering the tools they use less useful.
This principle is of particular importance, because the choice of programming tools
is one of the most sensitive areas with regard to the be programmer friendly goal. Programmers productivity is directly related to the tools that they use, and to which they are
used to.
One of the consequences of this principle, in what concerns the work presented in
this dissertation, is that I should not change the programming language. For instance,
some of the new constructs I propose would be easier to usefrom the programmers
point of viewif I extended Javas syntax with new keywords. Unfortunately, changing
the syntax of Java interferes with many tools. The compiler is one of them: If we extend
the language, then either a new compiler is needed (preventing the use of the existing
compilers), or a pre-processor should be provided that transforms the extended Javas
syntax into standard Java (therefore allowing the use of standard compilers). The compiler, however, is not the only tool that depends on the syntax of the language; other such
tools are modern development environments that provide editors with syntax highlighting
and auto-completion based on the parsing of the program source. These tools are severely
impaired by the change in the syntax, also.
So, instead of extending the language, most of the extensions proposed in this dissertation were developed so that (1) they can be used by using only standard Java syntax;
and (2) they are implemented in pure Java, therefore allowing the use of all the de-facto
standard Java tools.
The notable exception to this pure-Java approach is the Domain Modeling Language,
described in Chapter 5. In this case, I propose an entirely new language, requiring a
new compiler to process it. Nevertheless, because of this principle, the language and the
compiler are designed to integrate well with the remaining tools, as I shall discuss later
in Section 5.1.
Principle 3 (Make New Constructs Simple To Learn And Use)
I should take into consideration the pragmatics of newly introduced constructs,
hiding the complexities inherent to their implementation, and making them easy
to learn and use.
A construct that is too complex is not used, or, worse, is used in the wrong way.
Therefore, I should make my constructs simple. One way to accomplish this goal is to
avoid adding many options to each construct: Instead of making an all-purpose construct
with many configuration options, I should make simpler, one-purpose-only, constructs
that can be combined into more powerful constructs. Another way of reaching the goal of
simple-to-use constructs is to design them in such a way that, in the common case, there

Introduction

is not much to specify. But, if we need more complex behavior, then we can use optional
configuration options to accomplish that behavior.
Additionally, the difficulty in learning a new construct depends on how well the new
construct integrates with the remaining constructs, either new or already existing. For
instance, if a new construct replaces existing constructsor provides an alternative way of
doing somethingthen it should not break the programmers expectations on how things
work. The key idea here is that, to have simple to use and to learn constructs, I should
avoid surprises. The new constructs semantics should be natural; the semantics should
be what a programmer expects, given her knowledge from the language or other languages
where a similar construct exists.
Finally, the simplicity of use of a construct is related, not only with its use when
developing new code, but also with its effect on other development activities, such as
refactoring and maintenance.

Principle 4 (Provide The Most Value With The Minimum Effort)


I should find a good compromise between the usefulness of a construct and the
effort needed to implement that construct.

This principle, unlike all the previous, aims primarily the concerns of whoever implements the constructs proposed here. As explained above, I implemented all my proposals
without extending the language. Nevertheless, I expect that, once tested and proved
useful, some of the extensions that I propose become incorporated in the programming
language. Therefore, they will have to be reimplemented in that new context.
Typically, more powerful constructs are more complex to implement, more costly in
terms of computational resources, or both. On one hand, if a construct is difficult to
implement, then tool suppliers will resist to implement that construct. On the other hand,
if the implementation of a construct is computationally inefficient, then programmers will
avoid the construct.
Therefore, new constructs should seek a compromise between the power they provide
and the effort needed to support them. In general, it is preferable to have less ambitious
constructs that, still, provide good value to their users, but that, at the same time, are
simple to implement.
Note, however, that, despite this principle, I will not restrain myself from presenting
new constructs that I find useful just because they are difficult to implement or their
implementation is computationally inefficient. Rather, I use this principle as a guide to
find the best compromises.

1.3 Thesis Statement

1.3

Thesis Statement

This dissertations thesis is that it is possible to simplify significantly the task of implementing an object-oriented domain model by making a small, non-disruptive, and easy
to implement set of additions to the current object-oriented programming languages.
Namely, that it is possible to achieve that goal by adding the following to a language:
Atomic actions with support for concurrency control and failure recovery
A declarative language for specifying the structural aspects of a domain model
Consistency predicates that validate an atomic action at commit-time
To validate this thesis, I give specific proposals in the dissertation for adding each of
these elements to the Java programming language. I describe in detail how these new
constructs integrate with the Java language and demonstrate, by comparison with the
current best-practices in implementing a domain model, what are the benefits of these
new constructs for the implementation task.
To show that the addition of these new elements may be made with small changes at
the programming language level, the specific proposals made in this dissertation have a
minimal interface, thereby requiring minimal learning effort from the programmers that
intend to start using them.
To demonstrate that such additions do not necessarily entail a disruptive change in
how programmers work, the proposals made in this dissertation integrate seamlessly both
with the Java programming language, and with the existing software development tools
and processes.
To demonstrate that these new constructs are easy to implement, I implemented all of
them and I give, for each of the proposals made, a detailed description of its implementation in this dissertation.
One of the additional benefits of implementing all the constructs is that, having a
practical implementation for all of them allowed me the opportunity to further validate
their effectiveness, by introducing their use in the development of a real-world large web
application. This web application is developed by a medium-sized team of programmers
and the readily adoption of the new constructs in the development of the application
demonstrates both the usefulness of the new constructs and their ease of use.
Furthermore, having these proposals deployed in a real-world environment for several
years gave me, not only the necessary feedback to make improvements, but also the possibility of collecting statistics about the workload of a typical domain-intensive application.
I report on these statistics to demonstrate that they confirm the assumptions that I used
in the design of some of the proposals of this dissertation.

Introduction

result returned by

methodCall

method m1
waiting on lock

horizontal bar
represents the
execution of

o1.m1(arg)

method called
within m1

methodCall(...) result
parallel execution of

o1.m1(arg)

o1.m1(arg)
and o2.m2()

o2.m2()

result

methodInstruction
method call
that starts the
execution of m2

Time
t1

o1 20
o2 "Hello"

instruction
executed by
m2 at this time

objects state
at instant t1

result returned
by o2.m2()

Figure 1.2: Graphical notation used to illustrate the concurrent execution of methods. Time progresses from left to right, and each horizontal bar represents the execution of a method. When I need to refer to method calls within another method, I
use a smaller horizontal bar super imposed on the bar of the calling method.

1.4

Notation

Throughout this dissertation, I will need to show source code that implements some part
of an application. I shall use always the same language for that: Java.
The code shown, however, will seldom be a complete Java class: To simplify the
presentation, I will show only the fragments of code that illustrate what is being discussed
at the time; it may be a class with just a few of its members, a single method, or even only
part of a method. To make clear that something is missing, I will often use the sequence
... in place of the code that is not relevant to the understanding of the fragment
shown.
I omit, also, the usual Java access control modifiers when their presence is not relevant
to the discussion.

1.5 Outline of the Dissertation

Besides Java code, I will also need to discuss the (eventually parallel) execution of
Java programs. To that end, I use the graphical notation shown in Figure 1.2 on the
preceding page.

1.5

Outline of the Dissertation

This dissertation comprises eight chapters:

Introduction. The first chapter establishes what is the subject of concern of this
dissertation. Specifically, it introduces the class of domain-intensive applications
and their implementation as the target of the work proposed here. Furthermore, it
describes a set of guiding principles that influence significantly the type of solutions
presented in the dissertation, and presents the thesis statement.
Motivation, Problem Statement, and Approach. Chapter 2 expands on the motivation
given in the first chapter and situates this dissertations work into the larger contexts
of software engineering and software development processes. It presents the domain
model as a central element in the development of a domain-intensive application and
introduces an extended example of a banking domain model, which is then used
throughout the remaining of the dissertation. Finally, it identifies more precisely
the problem that this dissertation addresses and describes the general approach
that was followed to solve the problems identified.
The Difficulties of Implementing a Domain Model. To illustrate the problems described
generically in Chapter 2, this chapter goes through the implementation of part of
the rich domain model introduced there, concentrating on the implementation problems for which this dissertation proposes solutions. In particular, it discusses the
difficulties of: implementing a domain model that is safe in a concurrent execution environment, implement the associations that are part of a domain models
structure, and implementing the domain models constraints.
Versioned Software Transactional Memory. Chapter 4 proposes the use of a Software
Transactional Memory as an enabling technology that allows a rather different way of
implementing a domain model. It describes a novel Software Transactional Memory
that was designed specifically for the needs of a domain-intensive application, and
describes a practical and robust implementation of the system proposed.
Domain Modeling Language. Chapter 5 proposes a new declarative language for the
implementation of a domain models structure in an object-oriented programming
language. It describes the syntax and the semantics of the language in detail,
and describes how a domain models structure described in that language may
be transformed into a conventional object-oriented programming language such as

10

Introduction

Java. For that implementation, it proposes a novel pattern for the implementation
of bidirectional associations.
Consistency Predicates. Chapter 6 expands on the examples of the banking domain
model to introduce a set of development problems that are raised by the current
way of implementing domain constraints. To solve those problems, this chapter
proposes a new programming construct that allow a much simpler implementation
of domain constraints. Finally, it describes how that new construct may be effectively implemented by leveraging on the support for atomic actions given by the use
of a Software Transactional Memory.
Validation. To validate the applicability of the work proposed in this dissertation,
Chapter 7 describes how it was used in the development of a large real-world web
application. Also, it compares the performance of the Software Transactional Memory described in Chapter 4 in a series of benchmarks available in that area.
Conclusions. Chapter 8 summarizes the main contributions of this dissertation and
discusses which new areas of research are opened by some of its proposals.

Chapter 2

Motivation, Problem Statement, and


Approach
In the previous chapter, I stated that the ultimate goal of this work is to simplify the
implementation of domain models in object-oriented domain-intensive applications. The
reasons for pursuing this goal, however, were only generally addressed. In fact, the only
justification that I gave was the increasing importance of domain-intensive applications
in the development of software.
In this chapter I expand on the reasons that led me to embrace this work. I begin,
in Section 2.1, by discussing the role that domain models play into the wider context
of a complete software development process; the purpose of this discussion is to clarify
why I concentrate on the implementation of domain models in this dissertation. Then,
in Section 2.2, I introduce an extended example of a rich domain model, with a twofold
purpose: (1) to be used in Chapter 3 to illustrate the problems with the current bestpractices for implementing domain models; and (2) to be used throughout the remaining
of the dissertation as a running example to demonstrate the applicability of my proposals.
In Section 2.3, I describe, in general terms, what are the specific problems that I am trying
to solve in this dissertation. Finally, in Section 2.4, I discuss two general approaches to
simplify the implementation of a domain model and identify which one I adopted in the
course of this work.
Even though the set of domain-intensive applications neither contains nor is contained
by the set of enterprise applications, there is a significant overlap between the two. In
fact, in what concerns the implementation of a domain model, they may be considered
practically the same. So, without loss of generality, in what follows I shall refer to some
literature on the development of enterprise applications to describe some of the bestpractices for the development of domain-intensive applications.

Also, given that the

context of this work is already well-established, I shall often use the term application,

12

Motivation, Problem Statement, and Approach

without further qualifications, to refer to a domain-intensive application.

2.1

Domain Models in the Software Development Process

The development of software may range from single-person small programs that comprise
just a few lines of code, to ultra-large-scale software systems with millions of lines of code
developed by large teams of people.
Whereas in the first case it may be relatively simple to develop the program in a completely ad-hoc way, in the latter case, the mere dimension of the system introduces problems in the management of the development process that are very hard to solve [Gabriel,
Northrop, Schmidt, and Sullivan, 2006; Polak, 2006].
In general, to be able to develop with success a non-trivial application, it becomes crucial to have some way to tackle the complexity of the software development process [Royce,
1987]; in a very general sense, this is the subject of the software engineering discipline.
The software engineering discipline comprises many different subareas of expertise
from requirements engineering to software maintenance, going through project planning,
domain analysis, or software testing, to name just a few. Each of these areas of expertise
addresses only part of the entire software development process.

2.1.1

Software Development as the Transformation of Artifacts

One way to look at the software development process is as a series of activities that either
create new artifacts from scratch or that transform previously built artifacts into other
artifacts. For instance, the activity of requirements elicitation typically produces one or
more documents detailing the requirements that describe the problem that the program
should solve; then, from these requirements, other activities produce other artifacts, such
as: a plan for the execution of the project, a set of documents describing the software
architecture of the system, or the source code of the program that solves the problem.
The exact nature of the activities and the artifacts of a software development process
may vary considerably from one process to another. They depend not only on the complexity of the system, but also on the methodology and on the development process model
that is adopted by whoever is developing the system.
Naturally, some of the activities are simpler than others. In some cases an activity
may be performed automatically by some tool, but in most cases they require hard human
labor and high expertise. The high complexity of software development results, precisely,
from the combined difficulty of performing each of its activities. Thus, a contribution
that simplifies one of the activities simplifies, also, the software development process as

2.1 Domain Models in the Software Development Process

a whole.
The work described in this dissertation addresses a very specific activitythe transformation of a domain model into the source code that implements it. This activity is
commonly called implementation or coding. It is, also, an activity that is always present in
a software development process, regardless of the methodology or process model chosen.
In fact, the only artifact that a software development process must always produce is
a computer program (or set of programs) that may be executed on a computer; it is the
execution of this program that solves the problem that led to the software development
process in the first place.

2.1.2

Domain Model: A Central Artifact in the Development Process

A domain model, like any model, is a representation of (part of) the real thing; in this
case, a representation of an applications domain. The domain of an application is the
sphere of knowledge comprising all the entities and the processes of the real world that
is the subject area of that application. A domain model is an abstraction that represents
only the parts of the applications domain that are relevant to the development of the
application.
A domain model may take many forms and may serve many purposes, but it is indispensable in the development of an application. Even if it only exists in the mind of the
person that is developing the application: After all, that person would not be able to write
the application if she had no knowledge about the applications domain.
Indeed, for many small programs, the domain model only exists in the mind of the
developer, and is never put into a more explicit and available form (other than the program
implementing it). Yet, for larger software systems, involving more people, it becomes
infeasible not to have the domain model explicitly represented. It may take the form
of a document in natural language, a formal representation using some kind of formal
language, or some combination of these. In fact, it is common, nowadays, to use the
UML language [Booch, Rumbaugh, and Jacobson, 1999] to represent parts of the domain
model and complement it with natural language descriptions.
The domain model is a useful artifact for most of the activities in a software development process. Its construction starts with the activity of requirements elicitation, evolves
through the analysis and the design phases, and influences such late phases in the
process as the quality assurance, and the system maintenance.
The domain model plays, therefore, a central role in the development of any software
system. Yet, despite its importance, only in the late eighties did the research community
of software engineering started to address the activity of domain modeling as a research

13

14

Motivation, Problem Statement, and Approach

problem in itself [Iscoe, Williams, and Arango, 1991].

Since then, domain modeling

became a well-established practice in the area of software development; it is now common


to see software developers starting the implementation of a program, regardless of its size
or complexity, by discussing over a set of UML class diagrams that correspond to part of
the applications domain model.
This trend towards domain modeling by the professionals in the area reflects, in my
opinion, the increasing demand for applications with broader scopes than ever: As the
applications scopes increase, so do their domains and, consequently, the need for domain
modeling.
So, explicit domain models and domain modeling become more important as we move
away from data-intensive applications into the development of domain-intensive applications.

2.1.3

Domain Model at Various Levels: Analysis, Design, Implementation

In this dissertation I do not address the task of building a domain model. Rather, I assume
that it is already built using standard practices in the area. Once it is built, however, it
needs to be expressed in softwarethat is, implemented in a program that may execute to
solve the applications problem. As mentioned above, the target of the research described
in this dissertation is precisely this transformation from a domain model to source code.
This is an area where improvements are particularly welcomed, because, in general,
the implementation is the most labor-intensive activity during the development of software. Not only because the program is the most detailed artifact of the entire software
development process, but also because implementing a domain model is not just a matter
of filling in the missing details; rather, it involves the conversion of the concepts of the
domain model, which are expressed using the constructs of a particular domain modeling
language, into semantically equivalent concepts that are expressed using the constructs
available at the programming language level. Thus, the difficulty of implementing a domain model is greater when there is a greater mismatch between the modeling language
and the programming language.
Traditionally, given the wide gap between these two languages, the common advice
in the software engineering area has been to consider intermediate artifacts to facilitate
this transformation. So, instead of implementing the domain modelwhich is primarily
an artifact of the analysis phasethe traditional software engineering best-practices prescribe the construction of an intermediate design model. The goal of the design model
is to bring the concepts of the domain model closer to the constructs available in the
programming language, thereby facilitating the domain models implementation.
Unfortunately, having yet another language to represent the design model compli-

2.1 Domain Models in the Software Development Process

15

cates the software development process and increases the probability of translation errors
among the different models. To solve this problem, recent research in the software engineering area propose to reduce the distance between the analysis model (also called
conceptual model) and the design model by using a single modeling language for both
models (see, for instance, [Evermann and Wand, 2005]). In fact, one of the goals of the
model-driven design approach is to merge the analysis and the design models into only
one model [Evans, 2003, Chapter 3].
As the difference between these two types of models is not relevant for what I present
in this dissertation, I will not distinguish between both types of models in the remaining
of this document. Instead, I shall use the term domain model to refer to the artifact from
which software developers start their implementation activity. Moreover, I shall refer
generically to the task of passing from the problem description to a domain model as the
design task, rather than talk about analysis and design separately.
Finally, even though my contributions are at the programming language level and are,
therefore, largely independent of the language chosen to represent the domain model, I
shall use only UML class diagrams and natural language descriptions to represent domain
models.

2.1.4

Standard Architecture for a Domain-Intensive Application

To build an application and have it work as expected, we have to consider several other
aspects, other than the implementation of its domain model:

User interface: There must be code in an application to present an interface to the


user, to accept the users data, and to validate that data before further processing.
These requirements, however, are not part of the domain model.

Rather, they

are usually represented in artifacts such as use cases, presentation models, and
navigational models, from which software developers implement the corresponding
code in the application.
System qualities: To satisfy architectural requirements such as security, scalability,
performance, or availability, an application must include code that implements the
tactics necessary to achieve those qualities.
Technological environment: Applications run in some execution environment and
must, therefore, have code that implements the interfacing with that environment.
For instance, code that accesses a database or that handles network requests.

Obviously, all these elements must be put together with the code that implements the
domain model to make a complete application. Whether the code that implements these

16

Motivation, Problem Statement, and Approach

Presentation

Domain

Data Source

Figure 2.1: The layered architecture from [Fowler, 2002]. This is a pure layered
architecture, where each layer (represented by a box) may use only the layer immediately beneath it.

various concerns is intermingled with one another or separated in its own module, is a
matter of software architecture.
The advantages of modularization are well-known in the area of software development [Parnas, 1972]. One of those advantages is the ability to separate by different
modules code that we expect to change at different rates, so that changes in one module
do not influence significantly code outside that module. The difficulty, of course, is in
knowing which parts of an application are expected to change more than others.
Yet, over the years, the software development industry converged into the common
assumption that changes in the code that implements the domain model of an application
occur independently of changes in the code that deals with the user interface, for instance;
likewise for the code that interfaces with the execution environment. Whereas the domain
model varies only with changes in the application domain, the other two vary either with
the available technology or the deployment environment.
This difference became more acute, first with the advent of the web-enabled applications, and then with the growing trend towards service-oriented architectures. In both
cases, organizations felt the need to deploy their existing applications using either new
ways of accessing the application, or using a rather different infrastructure, while retaining the same domain logic in the application.
Therefore, it is now a common practice in the area of enterprise software development to adopt a layered software architecture for the development of an application.1
In particular, an architecture that separates the implementation of the domain model
from the remaining aspects of the application. For instance, Fowler [2002] proposes the
three-layered architecture that I show in Figure 2.1, whereas Evans [2003] proposes the
architecture shown in Figure 2.2 on the next page.
1

For a good presentation of the layered architectural style, see [Bass, Clements, and Kazman, 2003]
and [Clements, Bachmann, Bass, Garlan, Ivers, Little, Nord, and Stafford, 2003, Chapter 2].

2.1 Domain Models in the Software Development Process

User Interface

Application

Domain

Infrastructure

Figure 2.2: The layered architecture from [Evans, 2003]. Unlike the layered architecture depicted in Figure 2.1, in this case each of the layers may use all the layers
below it: The User Interface layer may use all the remaining layers; the Application layer
may use both the Domain and the Infrastructure layers; and the Domain layer may use
only the Infrastructure layer.

Although the two architectures are not exactly the same, in both cases there is a
domain layerthat is, a module that contains the implementation of the domain model
separated from the remaining aspects of the application (aspects such as the user interface). Moreover, this layer is restricted to use only the layer beneath it: the layer providing
infrastructural support. Thus, to understand the implementation of a domain model we
may need to consider also the infrastructural layer, but nothing else.

2.1.5

The Domain Layer Dependence on the Infrastructural Layer

Ideally, the domain layer should remain independent of any other layer in the system.
Given that the domain layer implements the applications domain model, it should not
be affected by changes that may occur in other parts of the application that deal with
aspects not related to the domain model.
In practice, however, in the standard architecture for an enterprise application, the
domain layer appears above the infrastructural layer. This dependence results from the
fact that the implementation of a domain model for an enterprise application needs to
take into consideration a range of other concerns that are extraneous to the applications domain model. These concernsoften called non-functional requirementsinclude
such things as: the need to support concurrent access to the domains entities, the need
to support the distributed execution of the application, the need to store persistently the
information about the domain, or the need to inter-operate and integrate with other external applications. So, whereas the responsibility of supporting most of these requirements

17

18

Motivation, Problem Statement, and Approach

rests upon the infrastructural layer, the domain layer may still need to use the infrastructural layers services to provide an implementation of the domain model that satisfies
these requirements.
A solution that is commonly used in an enterprise application, and that addresses
many of these requirements, is the use of an external database management system to
store persistently the information about the domains entities. Unfortunately, this common approach has the undesirable consequence of influencing the programming model
that is used to implement the applications domain: Notwithstanding the best efforts of
patterns, frameworks, and tools, that try to isolate the implementation of the domain
model from the specifics of persistently storing the information about the domain,2 that
isolation is not perfect and the usefulness of the object-oriented programming languages
becomes severely impaired, thereby further adding to the difficulty of implementing a
domain model.
Furthermore, by conflating the solutions of all the problems into a single approach
makes it more difficult, not only to analyze each of the problems and to improve on each
of the solutions separately, but also to employ that solution in contexts where not all the
requirements existfor instance, when no need for persistence exists.
Therefore, in this dissertation I shall ignore all the extraneous requirements for a
domain model and concentrate instead on how best can we implement the domain models
core requirements with the current object-oriented programming languages.

2.2

Example of an Application in the Banking Domain

To discuss the difficulties of implementing a domain model, I shall use throughout this
dissertation a simple example of an application from the banking domain.
The banking domain is a well known example, often used in the literature of objectoriented programming. But, whereas in most cases, the example is given with only a
couple of entities and relationships, in this section I extend the example with more entities
and relationships; also, entities have more properties and more complex behaviors than
usual.

2.2.1

Examples Rationale

Although this extended example is still an oversimplification of a real problem domain, it


exhibits some of the complexities that are typical of rich domain models. Thus, it suffices
to illustrate the problems that programmers face when they implement such domain
2

In fact, many of the patterns described, for instance, in [Fowler, 2002; Alur, Crupi, and Malks, 2001]
target specifically this problem.

2.2 Example of an Application in the Banking Domain

models. In fact, I want to show that, even in such simplistic examples, we encounter
problems that are difficult to solve using current object-oriented languages.
By presenting this extended example, however, I do not intend to discuss its implementation in all the details. Also, I shall not discuss all the possible alternatives either
for the design of a solution, or for the implementation of a given design, as they are too
numerous. Rather, I shall present a design that I deem as reasonable, and, for that design, I shall concentrate only on the implementation aspects that illustrate the problems
of the current approaches and the advantages of my proposals.
Finally, I shall use this example, not only as a means to introduce the problems that
I propose to solve in this dissertation, but also to introduce some terminology related to
the development of domain models.

2.2.2

Applications Functionality

The core functionality of the banking application is the management of the accounts that
belong to the clients of a bank. The clients accounts may be either checking accounts,
or savings accounts. Clients may have as many accounts of each kind as they want, but
they must own at least one checking accountthus, when a new client is added to the
bank, a new checking account is created for that client. Each account, on the other hand,
is owned by a single client.
As usual, accounts have a current balance, which corresponds to some monetary
amount in some particular currency. Deposits and withdrawals into an account change
the accounts current balance by the amount deposited or withdrawn. Yet, when the
amount to deposit or to withdraw is not in the same currency as the accounts balance, the
amount deposited or withdrawn must be converted to the accounts currency. Moreover,
depending on the currencies involved in the exchange and on the account for which the
exchange is made, the bank may charge the requesting account with a currency-exchange
fee.
Whereas clients are allowed to make as many deposits and withdrawals as they want
from their checking accounts, the operations on savings accounts are much more restricted. Savings accounts are created for a given time periodfor example, seven days,
or three monthsand with a corresponding interest rate. Clients may create a new savings account whenever they want by choosing one time period, and by transferring some
amount from one of their checking accounts. At the end of that time period, the bank
automatically transfers the savings accounts balance, plus interest, to the checking account that was used to create the savings account. Therefore, clients may earn money (in
the form of interest) by putting their money in savings accounts. The tradeoff is that they
cannot make further deposits into the savings account, nor make partial withdrawals.

19

20

Motivation, Problem Statement, and Approach

Clients, however, are allowed to withdraw all of the savings accounts balance before the
end of the time period is reached, but in that case the bank does not pay interest. Instead,
the bank charges a fee for the anticipated withdrawal. Finally, once the balance of the
savings account is withdrawn, either by the client or by the bank, the account is closed.
Checking accounts may be closed, also, provided that their balance is zero, and that
all of their corresponding savings accounts are closed. Naturally, neither deposits nor
withdrawals are allowed for a closed account.
The bank allows that checking accounts have negative balances, but the total balance
of each clientthat is, the sum of the balance of all the clients accountsmust be greater
or equal to zero. A checking account with a negative balance, however, pays interest to
the bank, on a daily basis.
At the end of each day, the bank calculates the interest due by each of the checking
accounts that have a negative balance, and charge each of the accounts accordingly. Only
then, it processes the savings accounts that end in that day. If, during this process, a
clients total balance becomes negative, all the accounts for that client are closed.
To pay the interest, or to receive the fees and the interest that is due in each of
the above mentioned situations, the bank itself has an account. This account has no
restrictions on its balance. Moreover, the deposits and withdrawals from this account
that need a currency conversion are not charged with a fee.
Finally, to issue periodic reports about the banks managed accounts, the bank needs
to calculate both the total of the clients checking accounts and the total of the clients
savings accounts. Each of these totals is the sum of all the corresponding accounts
balances.

2.2.3

Basic Domain Modeling Terminology

From the description of the banking applications responsibilities in the previous section,
it is straightforward to identify some of the objects that are relevant to the functioning
of the applicationobjects that belong to the applications domain. For instance, objects
such as bank, client, checking account, savings account, monetary amount, currency, and
time period result from the simple approach of identifying the nouns used in the description of the functionality. Typical object-oriented analysis and design methodologies3
start with these objects and proceed by identifying the attributes and the behaviors that
characterize each of the objects, as well as the associations among them.
It is common practice, however, to distinguish between entities and value objects (see,
for instance, [Evans, 2003, Chapter 5], [Fowler, 2002, Chapter 18], and [Riehle, 2000,
3

For an example, see [Booch, 1994].

2.2 Example of an Application in the Banking Domain

Chapter 3]). Entities are objects that have a distinguished identity in the program. On
the contrary, value objects do not have a distinguished identity. Rather, they are used
only for their value. Thus, we can replace an instance of a value object by another that
has the same value without affecting the semantics of the program. Whereas both kinds
of objects typically have attributes that describe the object state, only entities may have
their state changed during the program execution; value objects are immutable and, once
created, represent always the same value. Examples of entities from the banking domain
are banks, clients, checking accounts, and savings accounts. Also from that domain,
examples of value objects are monetary amounts, currencies, and time periods.
This distinction between entities and value objects extends naturally to their types.
So, I shall use the terms entity type and value type to refer to the type of a particular
entity and value object, respectively. As a matter of fact, the types, rather than their
instances, are the elements that are most used in the representation of a domain models
structure.
Besides entity types and value types, the other elements which are essential to represent the structure of a domain model are associations.4 Associations represent relationships between entitiesfor example, the relationship between clients and their accounts
is represented in the domain model by an association between the client type and the
account type. Binary associations may be either unidirectional or bidirectional, depending on whether they are traversable in only one or in both directions, respectively. A
binary association between two types A and B is traversable from A to B if the instances of
A can refer to the instances of B with which they relate to. Also, following the terminology
used in the UML 2.0 specification [Object Management Group, Visited in 2007], I shall
use the term link to refer to an instance of an associationthat is, the pair of objects that
relate to each other.
But, naturally, not all domain knowledge is structural. In a typical domain model,
such as the one described above, entities are subjected to constraints, also. A constraint
on one or more entities is a condition that those entities must satisfy. For instance, in
our example, that the total balance of each client must be greater or equal to zero.
Finally, another crucial element of a domain model is the description of its processes.
A process is a sequence of domain activities, possibly affecting some entities, that is
executed for a given purpose. It is important to know, not only which processes exist, but
also what they do, and how they do it. An example of a process in the banking domain is
the daily processing of the interest due by each of the checking accounts.

Even though some modeling methodologies and languages distinguish between different types of
relationshipsfor example, aggregations and compositionsin this dissertation I do not make such a distinction and consider only associations.

21

22

Motivation, Problem Statement, and Approach

bank 1

client 0..*
1
owner

Bank

1
owner

Client

bank 1

assets 1

Bank Assets

checking accounts 1..*

Checking Account

savings accounts 0..*


0..*

1
checking

savings

Savings Account

Figure 2.3: First design for the banking domain model. In this design, only the
classes and the associations that are mentioned explicitly in the problems description
are used.

2.2.4

Initial Design of the Banking Domain Model

Different software development processes, by definition, advocate different methodologies


for the development of an application. Heavy-weight software development processes try
to start with a complete elicitation of requirements and with a detailed planning of the
development. Agile processes, on the other hand, start by concentrating on the users
requirements with higher priority and on the rapid development of application prototypes
that embody part of those requirements.
Yet, regardless of which process is used, software developers have to make at least
a minimal set of design decisions before they start implementing an object-oriented application. Indeed, before programmers start making new classes in some object-oriented
programming language, they have to decide which classes they need for the problem
that they are trying to solve. Thus, a common artifact that is developed during the early
phases of an object-oriented development process is the applications class structurethat
is, which classes and which associations exist in the applications domain model. The
de-facto standard used to represent such class structures is the UMLs graphic notation
for class diagrams. Often, several class diagrams are used to represent an applications
class structure, each one of the diagrams showing part of the class structure. In this
case, however, because the domain is simple, I can show the entire class structure in a
single class diagram. For instance, in Figure 2.3, I show the entire class structure of a
possible design for the functionality described in Section 2.2.2.
The design depicted in Figure 2.3 contains classes for each of the entities mentioned
in the description of the banking application functionalities. In fact, I had identified
already all but one of these entities in Section 2.2.3. The new class in the diagram is
the class bank assets, which represents the account used by the bank to pay and to
receive the money that is due as a result of the various banking operations. Regarding
the relationships between domain entities, again, the associations used in this design

2.3 Problem Statement

correspond to each of the relationships that are explicit in the problems description.
But, from an object-oriented programming perspective, this design has a few shortcomings. Checking accounts, savings accounts, and bank assets are all different kinds of
accounts, with things in commonfor example, all the accounts have a current balance
and an operation that allows us to deposit some amount. Yet, this design fails to capture
this commonality. Likewise, both the checking accounts and the savings accounts are
client accounts, for which fees may be charged when the amounts deposited or withdrawn
are in a different currency.
Moreover, although with this design we can reach all the accounts of a bank by
going through the banks clients (who own the accounts), it is easier to implement some
of the applications functionalities if there is an association between the bank and its
clients accounts. As a matter of fact, it is useful to have several of such bankaccount
associations. For instance, for the daily processing of the checking accounts with a
negative balance, it would be helpful to have in the bank the set of all the checking
accounts with a negative balance; for processing the savings accounts that reached the
end of their time period, it would be helpful to have in the bank a list of savings accounts
sorted by their term date; and, for finding a client account given its account number, it
would be helpful to have in the bank a map that indexes the banks accounts by their
number.
In Figure 2.4 on the next page, I show an alternative design that takes some of these
aspects in consideration. Note that the two associations that, in the first design, exist
between the client and the two kinds of clients accounts, are replaced in this second
design by a single association between the client and the class that generalizes both kinds
of accountsthe class Client Account. Yet, whereas in the first design it was explicit in
the class structure that each client must have at least a checking account, in the second
design this structural restriction is no longer represented in the class diagram.
Even though the design space for this problem has many other solutions, I shall not
discuss them here. I remember that my goal with this exercise is not to discuss which
design is best. Rather, I want to discuss the implementation of domain models. Thus,
I shall use this second design as a reasonable solution to this problem and base my
discussion on it, which I shall do in Chapter 3.

2.3

Problem Statement

To implement a domain model, we must find appropriate representations for all the domain models elements in our programming language. In this task lies much (if not most)
of the software development complexity.

23

24

Motivation, Problem Statement, and Approach

Account

Bank Assets

Client Account

1..*

1
owner

account

Client
client 0..*

Checking Account
0..*

1
checking

0..*
savings

Savings Account
0..*
1
0..1

Bank

0..1

Figure 2.4: Second design for the banking domain model. This design has a more
complex structure than the design presented first. Two new classes were added to
capture the commonality between the different kinds of accounts. Moreover, in this
design there are two redundant associations between the class Bank and the classes
Checking Account and Savings Account, to simplify the banks daily processing of
accounts.

2.3 Problem Statement

The use of the object-oriented programming paradigm helps to some extent. For
instance, we may use classes to represent entities types, slots to represent entities attributes, and methods to represent some of the domain models processes. Unfortunately,
this apparent ease of implementation does not extend naturally to all the remaining concerns of a domain models implementation.
Indeed, I argue that what makes the implementation of a domain model such a difficult
task is that the current object-oriented programming languages lack the appropriate
expressiveness to implement some of the typical requirements of a domain model.
In this dissertation, I address specifically the following areas where current objectoriented programming languages have manifest shortcomings:
Representing associations: Even though associations are essential in the representation of a domain models structure, their implementation is not as simple as the
implementation of other structural aspects of a domain model, as we shall see in
Section 3.3. On the contrary, implementing correctly an association between two
entities may turn into a difficult task; more so, if we consider bidirectional associations, which must remain consistent even in the presence of failures during the
establishment of a link between two objects.
Representing constraints: Like associations, constraints on domain entities are
present in every domain model. But, whereas implementing very simple constraints
on a single object may be relatively easy, implementing constraints that involve
more than one object becomes very difficult on a moderately complex domain, as we
shall see in Section 3.4.2.
Concurrent execution: A common requirement for enterprise applications is that
they be able to process multiple requests from their users simultaneously. As the
common solution for this problem is to have each request processed by a different
thread, we have to face the difficulties of concurrent programming. The implementation of a domain model is particularly sensitive to this fact, because domain
entities are implemented as shared mutable objects that must maintain their identities throughout the entire applications life-cycle. Thus, we must ensure that these
objects are consistently manipulated in a concurrent setting, which is not at all
simple with the current object-oriented programming languages, as we shall see
in Section 3.1.
Failure Recovery: Domain operations may fail to complete, either because their
execution would violate a domain constraint, or simply because they received erroneous information. Most often, when that happens, nothing should change in
the domains state. But, given that current programming languages have no provision to undo changes, the implementation of domain operations must verify all
the preconditions necessary for the success of the operation before they make any

25

26

Motivation, Problem Statement, and Approach

change, or else, they must explicitly undo the changes in case of failure. This style
of programming, however, is very difficult to use in more complex operations, as we
shall see in Section 3.2.

2.4

Two Approaches to Solve the Problem

Now that I explained why implementing a domain model is an important and difficult
task, and that I want to contribute to its simplification, the question that remains to
be answered is: How do I propose to accomplish it? The full answer to this question,
of course, lies ahead in the remaining of this dissertation. But, before I conclude this
chapter, I present two general and complementary approaches that we may use to solve
this problem, so that I make clear which approach I am following in this work.

2.4.1

The MDA approach

One way to simplify the implementation of a domain model is to automate it. The general
idea in this case is to use code generation tools to generate automatically all the source
code necessary to implement a domain model, much in the same way as we use a compiler
to generate an executable program from its source code.
This approach is called the model-driven development approach [Selic, 2003] and is
best illustrated by the proposal of the Model Driven Architecture (MDA) framework by the
Object Management Group [Mellor, Scott, Uhl, and Weise, 2004; OMG, 2007]. The MDA
approach proposes that, rather than implementing an application in a specific execution
platform, developers should specify instead a platform independent model (PIM) for their
application; then, this PIM is transformed into a platform specific model by way of MDA
tools that generate all the code.
To accomplish this level of automation, however, the PIMs must be much more detailed than domain models typically are, or even than currently used domain modeling
languages allow them to be. Indeed, this need for operational models brought up by the
MDA approach is one of the major incentives for much of the work on the UML 2.0 specification [Object Management Group, Visited in 2007] and on the idea of an executable
UML [Raistrick, Francis, and Wright, 2004].
Yet, even though the MDA approach is enticing, not all researchers agree that it is the
best approach to solve the software development process [Thomas, 2003; Thomas and
Barry, 2003], or that UML provides a good foundation for it [Hailpern and Tarr, 2006;
France, Ghosh, Dinh-Trong, and Solberg, 2006].
In fact, it is not clear whether an MDA approach is feasible with current proposals.

2.4 Two Approaches to Solve the Problem

Also, even if we consider that it becomes a reality, it is questionable whether the development of the PIMs would be any simpler than implementing the source code, given the
level of detail necessary to make them fully executable. Finally, if the PIMs are meant to
be our domain models, then they loose part of their usefulness of being a higher-level and
abstract description of our programs; if, on the other hand, the PIMs are not meant to be
the domain models, then we have to face again the task of transforming a domain model
into a PIM, bringing us back to where we started.
A less ambitious approach, but still with the same general idea of generating code
automatically, is to consider only a fragment of the UML language, generate automatically
code for that fragment, and let the programmers complement the generated code with the
missing pieces that are necessary to complete the program. For instance, some proposals
along these lines exist for generating a set of classes in an object-oriented programming
language given a UML class diagram describing those classes [Harrison, Barton, and
Raghavachari, 2000; Gnova, del Castillo, and Llorens, 2003]. One of the difficulties
in this case is to avoid round-trip problemsthat is, that the code may be regenerated
without throwing away the changes that the programmers made manually.

2.4.2

This Dissertations Approach: Reduce the Gap between Languages

Most of my proposals in this dissertation follow an entirely different route (with a notable
and justified exception that I shall present in the end of this discussion).
As we saw before, the difficulty in implementing a domain model is caused by an
impedance mismatch between the two languages used in this task: the modeling and
the programming languages. So, another way to alleviate the problem is to reduce that
impedance mismatch, by bringing the two languages closer together.
In principle, if we make our programming language more expressive, we expect that
our implementation tasks become easier in return. In fact, it is not only a matter of being
more expressive in general, but of being more expressive towards our domain modeling
languagethat is, of having in the programming language constructs that correspond
more closely to the constructs that are used at the domain modeling level.
Object-oriented programming languages are an excellent example of the effectiveness
of this approach. By adding to procedural programming languages constructs such as
classes, objects, inheritance, or polymorphism, programming language researchers created a new programming paradigm that allows us an arguably simpler implementation
of our domain models; this happens because the newly added constructs allow a more
direct representation of the domain modeling concepts. For instance, implementing an
entity type as a class in an object-oriented programming language is much simpler than
in a procedural language that does not provide an equivalent construct.

27

28

Motivation, Problem Statement, and Approach

Certainly because of their adequacy for implementing domain models, object-oriented


programming languages turned into the mainstream languages for most of the current
development of domain-intensive applications. Still, there are plenty of space for improvements.
To summarize the general approach that I follow in this dissertation: I propose to extend object-oriented programming languages5 with constructs that facilitate the mapping
from a domain model to its implementation.
More specifically, I propose the addition of both atomic actions and consistency predicates to the programming language. Atomic actions will be discussed more thoroughly in
Chapter 4. My proposal of consistency predicates, which leverages on the support given
by atomic actions, is detailed in Chapter 6.
Also, I propose in Chapter 5 a new language to implement the structural aspects of a
domain model: the DML language. Although the design of DML remains faithful to the
approach that I just described, it differs from the remaining proposals in that it is not an
extension to an existing programming language. Rather, it is an entirely new language,
for which I implemented a compiler that generates Java source code. In this regard, it
resembles one of the MDA-like approaches that I described earlier in this section.
Yet, whereas in those MDA-like approaches the central idea is to generate code from
a domain model expressed in a modeling language such as UML, I propose DML as a
programming language. That is, the purpose of DML is not to replace any of the existing
domain modeling languages, but to serve as a language to implement domain models.
Finally, even though all of my proposals specifically target the implementation task, I
expect them to have repercussions in other phases of the software development process
as well. A similar effect occurred with the use of object-oriented programming languages:
Their use fostered the use of object-oriented analysis and design methodologies.

2.5

Summary

This chapter establishes the context of this research work in the larger context of the
software engineering discipline. In particular, it identifies the task of implementing a
domain model as the target of the work.
After discussing briefly what is a domain model, I introduce an extended example of
a rich domain model that is used throughout the dissertation. This example serves also
to introduce some basic terminology on domain modeling.
5

Even though all the work in this dissertation is specialized for the Java programming language, most of
the results apply equally to any other mainstream object-oriented language.

2.5 Summary

Then, I introduce the implementation problems that I propose to solve with this work.
Namely, the difficulty in implementing associations, domain constraints, and domain
operations that may fail, all of these in the context of a concurrent program.
Finally, I present my approach to solve the problem, which consists in extending the
programming languages with new programming constructs that allow them to implement
the domain model elements more easily.

29

30

Motivation, Problem Statement, and Approach

Chapter 3

The Difficulties of Implementing a


Domain Model
The specific problems that I propose to solve with this work, were already introduced
in Section 2.3. In this chapter, I get into the details of implementing part of the domain
model for the banking application. The goal of this exercise is to illustrate the difficulties
that programmers encounter when they have to implement rich domain models with
current object-oriented programming languages.
Following the same order on which the solutions shall be presented later, I begin,
in Section 3.1, with the problem of implementing a domain model in a concurrent program, followed, in Section 3.2, by the problem of implementing operations that may fail.
The solution proposed for both of these problems is the use of atomic actions, which are
described in Chapter 4.
Then, in Section 3.3, I address the problem of implementing the structural aspects
of a domain model, giving special attention to the implementation of associations. My
proposal to simplify the implementation of a domain models structure is to use the DML
language, which is presented in detail in Chapter 5.
Finally, in Section 3.4, I concentrate on the implementation of the domain models
behavior and constraints. To solve the problems identified in this latter section, I propose,
in Chapter 6, the use of consistency predicates.

3.1

Implementation of a Concurrent Domain Model

To demonstrate the problems that programmers face when they have to implement a
concurrent domain model, we do not need to go much farther in the complexity of a
domain model. In fact, even in rather simplistic examples we encounter problems that

32

The Difficulties of Implementing a Domain Model

class Account {
long balance;
Account(long balance) {
setBalance(balance);
}
long getBalance() {
return this.balance;
}
void setBalance(long balance) {
this.balance = balance;
}
void withdraw(long amount) {
setBalance(getBalance() - amount);
}
void deposit(long amount) {
setBalance(getBalance() + amount);
}
}
Listing 3.1: A non-thread-safe class Account in Java with the basic getBalance,
deposit, and withdraw operations.

are already difficult to solve using current object-oriented languages.


So, in this section, I shall use a stripped-down version of the banking application
introduced in Section 2.2. I start only with accounts and their balances, ignoring that
the accounts balances may be in different currencies.
As operations, consider that we may consult an accounts balance, deposit some
amount, or withdraw some amount.

3.1.1

Basic Thread-Safety

In Listing 3.1 I show a reasonable implementation of the class Account in Java, with
no concerns for thread-safety.
The lack of thread-safety in the class Account means that, in the presence of multiple
executing threads, the interleaving of the various threads can cause inconsistent updates
(and readings) of the instance variable balance: We can have an account with a balance
of 100, make two deposits of 50 each and arrive at a state where the final balance is 150,
instead of 200. This situation is illustrated in Figure 3.1 on the next page, where I show

3.1 Implementation of a Concurrent Domain Model

33

setBalance(150)
getBalance() 100
acc.deposit(50)
acc.deposit(50)
getBalance() 100
setBalance(150)
Time
t1

t2

t3

acc 100

acc 150

acc 150

Figure 3.1: The possible execution of two concurrent calls to the method deposit for
the same Account instance. Because no synchronization exists, the final accounts
balance is not correctone of the deposits was lost. The notation used in this Figure
was introduced in Figure 1.2.

class Account {
...
synchronized long
synchronized void
synchronized void
synchronized void
}

getBalance() {...}
setBalance(...) {...}
withdraw(...) {...}
deposit(...) {...}

Listing 3.2: The thread-safe version of the class Account. Thread-safety is obtained
by adding the synchronized modifier to all the classs methods.

two concurrent executions of the method deposit for the same Account object, acc.
Both executions call the method getBalance before any change is made to the balance
of the account. Thus, both obtain the same resultthe balance of the account before any
of the two deposits. Then, each execution of the method deposit adds to that value the
amount to deposit and sets the new balance. The problem is that the final call to the
method setBalance overwrites the change made by the previous execution.
This problem is widely known, of course, just as well as its trivial solution: To make
the class thread-safe we just have to add the synchronized modifier to each of the
methods in the class, as sketched in Listing 3.2. When a thread executes a synchronized
method on a particular object, the thread must acquire, first, an exclusive lock associated
with that object, which is released at the end of the method execution. While the thread is
holding the exclusive lock, no other thread can acquire the same lock. So, the execution
of a synchronized method for a given object cannot be interleaved with the execution, by

34

The Difficulties of Implementing a Domain Model

setBalance(150)
getBalance() 100
acc.deposit(50)
acc.deposit(50)
getBalance() 150
setBalance(200)
Time
t1

t2

t3

acc 100

acc 150

acc 200

Figure 3.2: The possible execution of two concurrent calls to the method deposit
for the same Account instance. In this case, because the method deposit is
synchronized, the second thread waits until the first thread finishes its methods
execution.

other threads, of any other synchronized method for the same object. If we make all the
methods of the class Account synchronized, then only one thread at a time can execute
code for each account object, therefore preventing inconsistencies. The new behavior of
the concurrent executions of the method deposit, after the addition of synchronization,
is depicted in Figure 3.2. In this case, when the second call to the method deposit is
made, the executing thread waits until the first thread releases the lock, before continuing
with the execution of the method.
Unfortunately, this simple solution brings its share of problems with it, also. For
instance, it prevents two threads from executing concurrently the method getBalance
for the same account, even though that method only reads the state of the object. In
this case, we are limiting the concurrency unnecessarily. In the end, putting too much
synchronization into the application eliminates all the parallelism from the application
(to the point of generating deadlocks). On the contrary, too few synchronization leads
to inconsistencies. Thus, the choice of adding a synchronized modifier should be
considered very carefully by each programmer, putting on the programmers shoulders a
great responsibility.
Furthermore, the problems become worse when more than one object needs to be accessed consistently by multiple threads. One of the problems with lock-based approaches,
as this one, is that these approaches do not compose: Given two thread-safe objects, we
cannot access both of the objects in a thread-safe way without adding more synchronization that, almost always, needs to override the existing synchronization. I shall discuss
now an example which illustrates this problem.

3.1 Implementation of a Concurrent Domain Model

Bank

belongs to

35

0..*
account

Account

Figure 3.3: The UML class diagram with the relationship between the class Bank
and the class Account: A bank can have many accounts, but an account belongs to
exactly one bank.

class Bank {
Set<Account> accounts;
...
void transfer(long amount, Account source, Account target) {
source.withdraw(amount);
target.deposit(amount);
}
long totalBalance() {
long total = 0;
for (Account acc : accounts) {
total += acc.getBalance();
}
return total;
}
}
Listing 3.3: The implementation in Java, without any concerns for thread-safety, of
the class Bank with the two methods transfer and totalBalance.

3.1.2

Thread-Safety with More than One Object

Continuing with our stripped-down example of the banking application, consider now
that the bank accounts are related to the bank, according to the UML class diagram
shown in Figure 3.3. Consider, also, that the class Bank has the following two methods:
(1) the method transfer, which transfers some amount between two accounts; and
(2) the method totalBalance, which calculates the total balance of all bank accounts
belonging to the bank.
I show, in Listing 3.3, a possible implementation in Java for the class Bank. Unlike
the methods of class Account, the methods shown for the class Bank do not change the
state of a Banks instance; the Banks methods just operate over the accounts of a bank.
Therefore, even though this implementation has no synchronization code, interleaved
executions of the method transfer do not interfere with one another, provided that
we are transferring money between instances of the thread-safe implementation of the
class Account shown before. Because the class Account is thread-safe, each deposit
or withdrawal leaves the corresponding account in a consistent state.
If we consider, however, the concurrent execution of the methods totalBalance

36

The Difficulties of Implementing a Domain Model

trg.getBalance() 100
100
src.getBalance()
totalBalance()

200

transfer(100, src, trg)


src.widthdraw(100)
trg.deposit(100)
Time
t1

t2

t3

src 100
trg 0

src 0
trg 0

src 0
trg 100

Figure 3.4: Execution of the method transfer during the execution of the method
totalBalance. The result returned by totalBalance is incorrect: The result is
200, but the correct total balance for the bank, either before or after the transfer, is
100.

trg.getBalance() 0

src.getBalance() 0

totalBalance()

transfer(100, src, trg)


src.widthdraw(100)
trg.deposit(100)
Time
t1

t2

t3

src 100
trg 0

src 0
trg 0

src 0
trg 100

Figure 3.5: Execution of the method transfer during the execution of the method
totalBalance. In this case, the balance of the src and trg accounts are read in
such a way that makes the result returned by totalBalance be 0. As in Figure 3.4,
however, the correct total balance for the bank, either before or after the transfer, is
100.

3.1 Implementation of a Concurrent Domain Model

and transfer, it is easy to see that the method totalBalance will return an incorrect
result, if it executes in the middle of the method transferthat is, immediately after
the withdrawal from the source account and before the deposit on the target account; at
that time, the total balance of the bank, as calculated by the method totalBalance,
does not correspond to the correct value. In fact, the method totalBalance can give an
incorrect result whenever the execution of this method is interleaved with the execution
of at least an execution of the method transfer in any of the following ways:

The method totalBalance reads the balance of the source account before the
withdrawal, and the balance of the target account after the deposit. In this case,
the result returned will exceed the correct value by the amount transferred. This
situation occurs in the execution shown in Figure 3.4 on the facing page.
The method totalBalance reads the balance of the source account after the
withdrawal, and the balance of the target account before the deposit. In this case,
the result will be incorrect, also, but now the value returned will be below the correct
value exactly by the amount transferred, as shown in Figure 3.5 on the preceding
page.

Unfortunately, solving this problem with lock-based mechanisms is not that easy.
On one hand, the simple solution of making both methods synchronized impairs greatly
the concurrency of the application. For instance, it would neither be possible to execute
concurrently two transfers between two pairs of unrelated accounts, nor call the method

totalBalance in two parallel threads. The problem here is that the lock on the bank
object is too coarse-grained. Moreover, it is reasonable to expect that bank accounts can
be accessed in other places other than these two methods of the class Bank. Having the

Banks methods synchronized does not help in getting the correct behavior in that case.
On the other hand, trying to use more fine-grained locks, by synchronizing on the
accounts as shown in Listing 3.4 on the following page, introduces other problems, such
as the possibility of deadlocksfor example, consider a thread transferring money from
account A to account B, and a concurrent thread transferring from B to A (see Figure 3.6
on the next page).
Solving the problems introduced by fine-grained locking (for instance, by acquiring
locks in a specific order to avoid deadlocks) makes the code much more complex and
is practically unfeasible for any moderately complex domain-intensive application, in
which the objects are densely intertwined. Rather, I argue that the only way to deal
effectively with this problem is by using the notion of atomic methods (or blocks), as
introduced by the work on Software Transactional Memories. In Chapter 4, I propose a
new practical implementation of Software Transactional Memory that is specially suited
for implementing rich domain models.

37

38

The Difficulties of Implementing a Domain Model

class Bank {
...
void transfer(long amount, Account source, Account target) {
synchronized (source) {
synchronized (target) {
source.withdraw(amount);
target.deposit(amount);
}
}
}
long totalBalance() {
return lockAndSum(accounts.iterator());
}
private long lockAndSum(Iterator<Account> accounts) {
if (accounts.hasNext()) {
Account acc = accounts.next();
synchronized (acc) {
long remainingSum = lockAndSum(accounts);
return remainingSum + acc.getBalance();
}
} else {
return 0;
}
}
}
Listing 3.4: An implementation of the class Bank that uses fine-grained locks on the
accounts accessed by each method. This implementation, however, does not handle
the problem of possible deadlocks.

synchronized (B)
synchronized (A)
transfer(50, A, B)

Deadlock!

transfer(50, B, A)

Deadlock!

synchronized (B)
synchronized (A)
Time
t1
Figure 3.6: Deadlock caused by the concurrent execution of two calls to the method
transfer, which synchronizes on the source and target accounts. The deadlock
occurs at time t1.

3.2 Failure Recovery

39

trg.deposit(100)
src.widthdraw(100)
transfer(100, src, trg)
Time
t1

t2

t3

src 100
trg 0

src 0
trg 0

src 0
trg 0

Figure 3.7: Failure during the deposit of a transfer operation. The cross in the
method deposit represents that the method throws an exception, causing the termination of both the deposit and transfer methods. The state of the program,
however, changed at instant t2 and, thus, remained changed after the exception:
Whereas at the beginning of the transfer the src accounts balance is 100, at the
end the balance is 0 and the account trg remains the same.

3.2

Failure Recovery

In the above discussion, I argued against the use of locks and in favor of using atomic
actions to ensure the consistency of a concurrent application. In fact, the concurrency
control aspects of atomic actions are the primary concern in most of the published work
in Software Transactional Memory research. Yet, for the purposes of this dissertation,
the problem of maintaining the consistency when a failure occurs during the execution of
an operation is an equally, if not more, important concern. This problem occurs whether
we have a concurrent application or not. Thus, it is independent of concurrency and it is
not solvable by the use of locks.
Consider, again, the method transfer shown in Listing 3.3 on page 35. Except
for the problems of concurrency already discussed, this method appears to be correct.
Unfortunately, the problem I am discussing in this section manifests itself even in this
simple method if the method deposit may fail in some cases.
Given the implementation of the class Account in Listing 3.1 on page 32, the methods

withdraw and deposit never fail.1 On a more realistic setting, however, neither can we
withdraw an arbitrary amount from an account, nor can we deposit on all accounts. For
instance, assume that we cannot withdraw an amount larger than the account balance,
and we cannot deposit on a closed account. In Listing 3.5 on the following page, I show
the changes made in the class Account to incorporate these new requirements.
1

Unless some runtime error, such as an out-of-memory error occurs. Nevertheless, I am not concerned
with runtime errors due to the malfunction of the program. Rather, I am concerned with the normal,
domain-dependent, exceptions that may occur.

40

The Difficulties of Implementing a Domain Model

class Account {
...
boolean closed;
...
void withdraw(long amount) {
if (amount > getBalance()) {
throw new InsufficientBalanceException(...);
} else {
setBalance(getBalance() - amount);
}
}
void deposit(long amount) {
if (closed) {
throw new ClosedAccountException(...);
} else {
setBalance(getBalance() + amount);
}
}
}
Listing 3.5: Reimplementation of the methods withdraw and deposit, for the
class Account, to limit the withdrawal of an amount to the balance of the account,
and to refuse deposits in a closed account.

3.2 Failure Recovery

void transfer(long amount, Account source, Account target) {


if ((source.getBalance() < amount) || target.isClosed()) {
throw new TransferException(...);
} else {
source.withdraw(amount);
target.deposit(amount);
}
}
Listing 3.6: Reimplementation of the method transfer to verify, before making any
change, that the whole operation will succeed.

With this new definition for the class Account, the method transfer is no longer
correct. Consider the case when we are transferring some amount to a closed account.
The call to the method deposit on the target account will fail, by throwing the exception

ClosedAccountException. But, because the failure occurs after the source account
was withdrawn, it causes the loss of the amount to transfer (see Figure 3.7 on page 39),
which is not an acceptable behavior for such an application. Note that this problem is not
related to concurrency. The case that I presented here is a purely sequential program.
To solve this problem, we have essentially two options:

We verify before executing any of the operations that they will not fail, as in Listing 3.6.
We save the previous state of the source account and restore it in case of failure, as
in Listing 3.7 on the next page.2

I argue that neither of these approaches is satisfactory.


In the first case, the need to verify in a method of the class Bank the preconditions
for executing the withdrawal and the deposit method calls, breaks the modularity and
encapsulation of the class Account: The responsibility of knowing when a withdrawal
or a deposit can be made should belong exclusively to the class Account. Ensuring this
property is essential, if we want to take advantage of common object-oriented programming facilities such as polymorphism. For instance, consider that we want to have a
different kind of accounte.g., a deposit accountin which deposits are further limited.
We want that the method transfer remains the same, independently of the new types of
accounts in our application. The alternative of adding to the class Account the predicate

canDeposit which receives the amount to deposit and returns whether that amount
can be deposited or not, is not viable, either: In the end, we have one such predicate
for each operation that may fail. Not only that, all invocations of the operations have to
2

In this particular case, we could just re-deposit the amount in the source account, undoing the original
operation. That approach, however, is not generally applicable, because the performed operation first may
be irreversible.

41

42

The Difficulties of Implementing a Domain Model

void transfer(long amount, Account source, Account target) {


long savedBalance = source.getBalance();
source.withdraw(amount);
try {
target.deposit(amount);
} catch (Throwable t) {
// the deposit failed, undo the withdrawal
source.setBalance(savedBalance);
throw t;
}
}
Listing 3.7: Reimplementation of the method transfer to undo the withdrawal
when the deposit operation fails.

be guarded by calls to these predicates. Finally, this approach is not applicable easily
in all cases. Sometimes, it is impossible to know in advanceor to factor them outthe
conditions that prevent the correct execution of each operation.
In the second case, we have similar problems. On one hand, the state of the object
may not be publicly available or it may not be easy to save. On the other hand, the
complexity of the code needed to save and restore the state increases substantially for
more complex methods, when several operations that may fail are executed within more
sophisticated control structures. I believe that the difference between the original version
of the method transfer and the version corresponding to this approach illustrates very
well this latter point.

3.3

Implementation of the Banking Domain Models Structure

In this and in the following section, I shall now consider the full domain model introduced
in Section 2.2.4.
Class diagrams, as those shown in Figure 2.3 on page 22 and in Figure 2.4 on page 24,
represent only (part of) the class structure of a domain model. The dynamic aspects of
a domain modelits behaviorare not captured by such diagrams. In fact, as I discuss
briefly at the end of Section 2.2.4, not even all the structural aspects of a domain model
are captured by class diagrams. To model the dynamics of an object model, modeling
languages such as UML provide complementary notations, such as sequence diagrams
and state charts.
This separation, at the modeling level, between the structural and the behavioral
aspects of a domain model, becomes diluted when we are at the implementation level,
because object-oriented programming languages conflate the two aspects into the same
artifactthe class. Yet, for the purposes of this presentation, it is useful to discuss the

3.3 Implementation of the Banking Domain Models Structure

implementation of each of these aspects in separate. So, in this section I shall discuss
only the implementation of the domain models structure. Then, in the next section, I
address the behavioral aspects of the domain models implementation.
The class diagram shown in Figure 2.4 on page 24 is a high-level class diagram. As
such, it omits many details which are essential for an implementation of the domain
modelfor example, no attributes are shown for the classes in the diagram. The omission
of such details during the initial design phase is an important abstraction mechanism
that allows us to experiment with different designs without spending much time with the
details of each one.
But, once a design is chosen, the class diagram must be filled-in with the details
necessary for an implementation of that design. In Figure 3.8 on the next page, I show
a class diagram in which some of the classes previously shown in Figure 2.4 have more
informationnamely, information about their attributes and their methods. The methods
in each class implement that class instances behavior; the attributes, on the other hand,
are used to hold the state of each class instance.
To implement the structure of a domain model, programmers must provide an implementation for each of the following two kinds of domain model elements: (1) the classes,
with their attributes; and (2) the associations between classes.

3.3.1

Implementation of Classes

The implementation of the basic structure for each of the classes in a UML class diagram
is straightforward in most modern object-oriented programming languages. Each of the
classes in a class diagram maps naturally into a programming language constructfor
example, a Java class or interface. Furthermore, UMLs generalization relationships3
between classes are implemented by the usual type extension constructs that are available
in object-oriented programming languages. Finally, for the UMLs class attributes we have
a direct correspondence with the class member slots (or fields) found in object-oriented
programming languages. For instance, using Java, each class attribute is implemented,
typically, as a private class field, with two accessor methods: one to obtain the field value,
and the other to set the field to a new value. The visibility of each of these class members
varies, depending on whether they are meant to be used outside the class or not.
In Listing 3.8 on page 45, I show a partial implementation, in Java, of the classes

Account and ClientAccount. Each class implements the attribute that is depicted
in Figure 3.8 on the next page.

Also called inheritance or is a relationships.

43

44

The Difficulties of Implementing a Domain Model

Account
balance:Money
deposit(Money)
withdraw(Money)

ClientAccount

BankAssets

Client

closed:boolean

1..*

close()
isClosed():boolean

account

1
owner

name:String
totalBalance():Money

SavingsAccount
CheckingAccount

1
checking

0..*
savings

interestRate:Percentage
depositPeriod:TimePeriod
termDate:Date

Figure 3.8: Implementation-level class diagram for part of the banking applications
domain model. This class diagram shows some of the classes from the design-level
class diagram shown in Figure 2.4, but with some implementation details added.
Both attributes and methods are shown for some of the classes.

3.3 Implementation of the Banking Domain Models Structure

public class Account {


private Money balance;
public Money getBalance() {
return this.balance;
}
protected void setBalance(Money balance) {
this.balance = balance;
}
}
public class ClientAccount extends Account {
private boolean closed;
public boolean isClosed() {
return this.closed;
}
public void close() {
this.closed = true;
}
}
Listing 3.8: Partial implementation of the classes Account and ClientAccount.
In this case, each UML class is implemented as a Java class. Also, the generalization
relationship between the entities Account and Client Account is implemented by making the class ClientAccount a subclass of Account. Finally, class attributes are
implemented as Java class fields with accessor methods.

45

46

The Difficulties of Implementing a Domain Model

3.3.2

Implementation of Associations

Unlike classes, associations do not have a direct mapping into the implementation-level
programming language. Thus, their implementation is not as simple as it is for classes.
In fact, in some cases, the implementation of associations is far from trivial and becomes
a great burden for the programmer.
Class associations are common at the modeling level, but they have not found their
way into the realms of modern object-oriented programming languages, even though
their absence at the implementation level has been noted two decades ago [Rumbaugh,
1987]. The lack of support for associations at the programming language level forces
programmers to use other constructs to implement them. In some cases that is easily
done; in many others, however, it is not.
One of the factors that influences the implementation of an association is its multiplicity. One-to-one associations are simpler to implement than many-to-many associations,
whereas one-to-many associations stay somewhere in between.
Another factor that affects the implementation of an association is whether the association is unidirectional or bidirectional. Bidirectional associations are harder to implement
than unidirectional associations. As a matter of fact, bidirectionality poses the most difficulties on the implementation of associations. So much so that programmers are advised
to avoid bidirectional associationsor to limit their useeven though such associations
are obviously necessary and useful for expressing the structure of a domain model. For
example, consider what Eric Evans wrote on his book on domain-driven design:

In real life, there are lots of many-to-many associations, and a great number
are naturally bidirectional. The same tends to be true of early forms of a model as
we brainstorm and explore the domain. But these general associations complicate
implementation and maintenance. Furthermore, they communicate very little about
the nature of the relationship.
There are at least three ways of making associations more tractable.
1. Imposing a traversal direction
2. Adding a qualifier, effectively reducing multiplicity
3. Eliminating nonessential associations
It is important to constrain relationships as much as possible. A bidirectional association means that both objects can be understood only together. When application
requirements do not call for traversal in both directions, adding a traversal direction
reduces interdependence and simplifies the design.

[Evans, 2003, page 83]

I agree with the argument that bidirectional associations increase the coupling between classes, and that they may, therefore, reduce our ability to reason about the domain
in a modular way. I do not agree, however, with the argument that we should remove

3.3 Implementation of the Banking Domain Models Structure

public class Bank {


private BankAssets assets;
public BankAssets getAssets() {
return this.assets;
}
public void setAssets(BankAssets assets) {
this.assets = assets;
}
}
Listing 3.9: Implementation of a unidirectional one-to-one association between Bank
and BankAssets. The field assets in the class Bank is used to keep a reference to the only instance of BankAssets that may relate with a bank. The class
BankAssets is not shown here because no change is needed in it.
bidirectional associations just because they are difficult to implement. Instead, I propose
that we make them simple to implement. That is the main goal of the language that I
propose in Chapter 5. But first, it is worthwhile to understand why are associations so
complicated to implement.
In fact, a class association may be implemented in several different ways (see, for example, Guhneuc and Albin-Amiot [2004]; Harrison et al. [2000]; Gnova et al. [2003]).
Here, I do not discuss all the possible implementations. Rather, I present some examples that follow the idioms that are commonly found in the literature of object-oriented
programming.
Unidirectional one-to-one associations
The simplest case is when we have an unidirectional one-to-one association. As an example, consider that we impose a traversal direction on the association between the class

Bank and the class BankAssetsthat the association only allows the traversal from
the Bank to the BankAssets. The implementation of this association is similar to the
implementation of any other class attribute for the class Bank: We add a field to the class

Bank to keep a reference to the only instance of BankAssets that is related with each
Banks instance, and we add the necessary methods to access that field. In Listing 3.9,
I show such an implementation. Note the similarity with the code from Listing 3.8 on
page 45.
Bidirectional one-to-one associations
Unfortunately, the implementation of a bidirectional one-to-one association is no longer
that simple: At least, an implementation that gives us some guarantee of domain consistency.

47

48

The Difficulties of Implementing a Domain Model

Consider again the case of the BankBankAssets one-to-one association, but now as
a bidirectional association. A simple implementation consists in keeping the class Bank
as shown in Listing 3.9 on the preceding page, and in changing the class BankAssets
so that this class has a reference to an instance of a Bank. The problem with this
implementation, however, is that there is nothing in it to prevent domain inconsistencies.
For example, this implementation allows that a client of these classes creates an instance
of the class Bank that has a reference to an instance of the class BankAssets, which
in turn has a reference to a different instance of the class Bank. Yet, in a bidirectional
one-to-one association, if an object A refers to another object B, then the object B must
refer to the object A also; otherwise, the relationship is not consistent. Of course, it
is possible to have such a simple implementation and still do not have inconsistencies,
provided that the client code of these classes create and change both classes consistently.
But this puts the responsibility on the wrong place.
A better implementation of this association should ensure that, when we execute
a method call such as bank.setAssets(assets), both the bank and the assets
are updated consistently. Moreover, the result of that method call should be the same
as executing assets.setBank(bank). In Listing 3.10 on the next page, I show an
implementation of the BankBankAssets bidirectional association that satisfies these
requirements.
The code in Listing 3.10 illustrates well enough why programmers avoid bidirectional
associations, if possible: Bidirectional associations are much more difficult to implement.
Although the implementation shown follows the recommendations of the Change Unidirectional Association to Bidirectional refactoring pattern [Fowler, Beck, Brant, Opdyke,
and Roberts, 1999], the mere application of the pattern is highly error-prone.

One-to-many and many-to-many associations


Going from one-to-one to either one-to-many or many-to-many associations adds complexity into the implementation, because, in the latter case, objects no longer refer to a
single object. Rather, they may refer to an indeterminate number of other objects. Therefore, the class fields that implement the associations ends that may refer to many objects
must be instances of a collection.
Fortunately, the Java standard platform provides us with a reasonable API for working
with a variety of collections. So, by using some classes from the Java Collections Framework, we may implement a unidirectional one-to-many association as shown in Listing 3.11 on page 50. Note that the method getAccounts must return a different set of
accountsa set of accounts that is unmodifiable. Otherwise, clients of this class would
be able to modify directly the relationships of a classs instance.
Changing the association from unidirectional to bidirectional, causes a change in the

3.3 Implementation of the Banking Domain Models Structure

public class Bank {


private BankAssets assets;
public BankAssets getAssets() {
return this.assets;
}
public void setAssets(BankAssets assets) {
if (this.assets != assets) {
if (this.assets != null) {
this.assets.friendSetBank(null);
}
if ((assets != null) && (assets.getBank() != null)) {
assets.getBank().setAssets(null);
}
if (assets != null) {
assets.friendSetBank(this);
}
this.assets = assets;
}
}
}
public class BankAssets {
private Bank bank;
public Bank getBank() {
return this.bank;
}
public void setBank(Bank bank) {
if (bank != null) {
bank.setAssets(this);
} else if (this.bank != null) {
this.bank.setAssets(null);
}
}
void friendSetBank(Bank bank) {
this.bank = bank;
}
}
Listing 3.10: Implementation of a bidirectional one-to-one association between Bank
and BankAssets. The class BankAssets delegates into the class Bank the responsibility of performing the changes needed in both classes. The code in the class Bank,
on the other hand, needs to ensure that old links are broken before creating the new
one.

49

50

The Difficulties of Implementing a Domain Model

public class Client {


private Set<ClientAccount> accounts;
public Set<ClientAccount> getAccounts() {
return new Collections.unmodifiableSet(this.accounts);
}
public void addAccount(ClientAccount account) {
this.accounts.add(account);
}
public void removeAccount(ClientAccount account) {
this.accounts.remove(account);
}
}
Listing 3.11: Implementation of the unidirectional one-to-many association between the classes Client and ClientAccount. The traversal direction is from
the Client to the ClientAccount. An instance of the class java.util.Set
is used to keep all the clients accounts. The set of accounts is protected against
modifications before returning it to a client of the class.

implementation which is similar to the change occurred in the one-to-one case. The code
needed, however, varies with each combination of multiplicities.
Furthermore, the choice of which collection to use to implement an association depends on additional properties of the association. If the association is ordered, for instance, then the most appropriate collection might be an ordered set or a list. If, on the
other hand, the association is qualified, then maybe a map would be preferable.
So, even though there are well-known patterns on how to implement the various types
of associations in an object-oriented programming language such as Java, the problem is
that employing those patterns is, nevertheless, a burdensome and error-prone task.

3.4

Implementation of the Banking Domain Models Behavior

Once we have the code that implements the basic class structure for the banking domain
model, we may turn now our attention to the implementation of the remaining functionality.
To implement the required functionality, we need to add behavior to the classes that
we have in the domain, either by writing new methods, or by adding new code to the
methods that already exist. In this section, I shall discuss some of these methods.

3.4 Implementation of the Banking Domain Models Behavior

public abstract class Account {


...
public void deposit(Money amount) {
amount = normalizeCurrency(amount);
setBalance(this.balance.add(amount));
}
public void withdraw(Money amount) {
amount = normalizeCurrency(amount);
setBalance(balance.subtract(amount));
}
protected Money normalizeCurrency(Money amount) {
Currency accountCurrency = getBalance().getCurrency();
if (accountCurrency.equals(amount.getCurrency())) {
return amount;
} else {
return getBank().convertTo(amount, accountCurrency, this);
}
}
public abstract Bank getBank();
}
Listing 3.12: Generic implementation of the methods deposit and withdraw for
the class Account. Each of the methods calls an auxiliary method that returns the
amount in the appropriate currency, converting it, if needed. The abstract method
getBank, called by the method normalizeCurrency, must be implemented by
each of the concrete subclasses of Account that have some association with the
class Bank. An instance of a Bank is needed to perform the currency exchange.

3.4.1

Implementation of the Basic Deposit and Withdraw Operations

Because many of the functionality requirements are related to the deposit and withdraw
operations, I start with the implementation of these operations. According to the class
diagram shown in Figure 3.8 on page 44, these two methods are implemented in the class

Account. In fact, even though they need to be specialized for some of the subclasses of
Account, their basic definition is the same for all the accounts: the method deposit
adds the given amount to the accounts balance; the method withdraw subtracts the
given amount from the accounts balance. The amount to add or to subtract, however,
must be in the same currency as the accounts balance. Thus, in Listing 3.12, both
methods use a helper method to perform the conversion of the amount to the appropriate
currency, if needed.
The Banks method convertTo is responsible for making the currency exchange of
the received amount to the new currency, charging the account received as the third
argument with the exchange fees that are applicable. In Listing 3.13 on the next page, I

51

52

The Difficulties of Implementing a Domain Model

public class Bank {


public Money convertTo(Money amt, Currency curr, Account acc) {
ExchRate rate = getExchangeRate(amt.getCurrency(), curr);
Money newAmount = rate.convertTo(amt);
if (acc.isClientAccount()) {
charge(acc, rate.getFee());
}
return newAmount;
}
}
Listing 3.13: Implementation of the method convertTo. The bank converts the
amount using the appropriate exchange rate and then charges the account with a fee
if the account in question is the account of a client.

show the sketch of a possible implementation for this method.


With this simple implementation, however, we are ignoring one problem: What happens if the call to the method setBalance fails?

Because the call to the method

normalizeCurrency is made first, it may have already charged the account for the
exchange fees, even though the operation will not conclude successfully. This is yet
another example of the difficulty in dealing with failures in domain operations. Unfortunately, fixing the code to handle this case makes the code much more complex; so, I will
leave the code as shown and assume that it is easy to fix it with the solution proposed in
this dissertation.

3.4.2

Implementation of the Clients Total Balance Limit

One of the functionalities described in Section 2.2.2 is that the total balance of each
client must be greater than or equal to zero. Therefore, to implement this functionality
we must prohibit the operations that, if executed, would cause the total balance of a
client to become negative. Because the only operation that decreases the total balance
of a client is the withdrawal of money from one of its accounts, we may try to implement
this functionality by overriding the method withdraw in the class ClientAccount, as
shown in Listing 3.14 on the facing page.
The strategy for this implementation is to abort the withdrawal before it occurs, by
checking first if the clients total balance is less than the amount to withdraw. Unfortunately, this solution has two errors. First, because the amount to withdraw may be in a
currency different from the currency of the result of totalBalance, the two values may
be incomparable.4 Second, because the call to the inherited method withdraw may, in
fact, withdraw more than the amount requested, if, for example, some fee is charged by
4

Although the class Money is not described here, I assume that it is not possible to compare directly
values with different currencies.

3.4 Implementation of the Banking Domain Models Behavior

public class ClientAccount extends Account {


public void withdraw(Money amount) {
if (getOwner().totalBalance().lessThan(amount)) {
throw new ClientNegativeBalanceException();
} else {
super.withdraw(amount);
}
}
}
Listing 3.14: Checking the clients total balance before the withdrawal is performed.
If the clients total balance is less than the amount to withdraw, then an exception is
thrown. Otherwise, the withdraw proceeds.

public class ClientAccount extends Account {


public void withdraw(Money amount) {
super.withdraw(amount);
if (getOwner().totalBalance().isNegative()) {
// assume that the already made side-effects are undone
// when the exception is thrown
throw new ClientNegativeBalanceException();
}
}
}
Listing 3.15: Checking the clients total balance after the withdrawal is performed. In
this solution, first the withdrawal is performed and only then we check to see whether
the clients total balance became negative. If that is the case, then an exception is
thrown and we rely on some failure recovery mechanism to undo the already made
changes.

the bank; if that happens, the comparison made in this method is useless to prevent that
the clients total balance after the withdrawal becomes negative.
Assuming again that we can undo the changes already performed by an operation that
fails, an alternative implementation that solves both problems is shown in Listing 3.15.
This implementation relies on the failure recovery capabilities of an operation to perform
first the withdrawal and only then check if the resulting state is consistent with the
domain requirements. If not, then an exception is thrown and the atomic execution of
the method withdraw is aborted, undoing the effect of the withdrawal. Yet, even though
this second solution corrects the two errors identified in the first solution, it is not free of
problems, either. In fact, there are at least two problems with it.
The first problem with this solution is that it may cause a failure, even if the withdrawal
is part of a complex banking transaction that, in the end, would leave the total balance
of the client positive. The failure happens if the withdrawal causes the total balance
to become negative, even if only temporarily. In fact, a simple example illustrates this

53

54

The Difficulties of Implementing a Domain Model

problem. Consider a client with two checking accounts: the first with a negative balance
of 100 EUR, and the second with a positive balance of 200 EUR. The total balance for this
client is 100 EUR. What happens if the client decides to transfer 150 EUR from the second
account to the first? If the transfer operation withdraws the 150 EUR from the source
account first, the total balance of the client becomes negative (minus 50 EUR), which
causes the withdrawal to fail. Of course that, in this case, it may be possible to reorder
the operations so that the deposit occurs before the withdrawal, but this reordering is not
always possible. For instance, consider the case of a deposit of an amount that must be
converted to another currency. As part of the deposit operation, before the balance of the
account is increased with the amount, the amount is converted to the correct currency
and the account is charged with a fee. If the client total balance is 0, then the deposit
fails, because the fee cannot be withdrawn.
So, if we cannot verify that the clients total balance is not negative at the end of the
method withdraw, where can we do it? I argue that we can make this verification only
at the end of the outermost domain operation. Unfortunately, we lack the mechanisms
to do it. In Chapter 6, however, I propose a solution to this problem.
The second problem with the implementation shown in Listing 3.15 is that it is in the
wrong place. The responsibility of checking that the clients total balance is not negative
should not belong to the class ClientAccount. Rather, it should be a responsibility of
the class Client. Imagine that we extend the domain of our example to support different
types of clients. Most probably, each type of client has a different condition regarding
its total balance. For instance, the rule we now have may not be applicable to business
clients, or to clients with a mortgage. Having the verification of the rule in the method

withdraw of the class ClientAccount does not allow us to differentiate between these
cases. If, on the other hand, the responsibility of checking the clients total balance is
on the class Client, then we can specialize it for different types of clients. One way
to implement this verification in the proper place is to use the Observer design pattern
(see [Gamma, Helm, Johnson, and Vlissides, 1995]), and make the client an observer of
all its accounts. But, even if the use of the observer pattern helps in localizing the code
in the proper place, it forces a more complex solution and does not solve the first problem
either. Again, I argue that the solution that I propose in Chapter 6 is a much cleaner way
of solving this problem.

3.5

Summary

This chapter gives several examples of how difficult can be the implementation of a domain
model. The four problems identified in Section 2.3 are illustrated in this chapter with
concrete examples from the banking applications domain model.

3.5 Summary

Even though the domain used as example is very simple, I show that its implementation is far from trivial; specially, if we consider that the domain entities are accessed
concurrently.

55

56

The Difficulties of Implementing a Domain Model

Chapter 4

Versioned Software Transactional


Memory
The hardware industrys commitment to the use of multi-core processors as the only
practical way to improve computing power for the new generation of computers, brought
concurrent programming, finally, into the realms of mainstream programming. Yet, almost all modern programming languages lack adequate abstractions for concurrent programming.
As we saw in Section 3.1, mechanisms such as Javas synchronized keyword are
useful to develop thread-safe single objects, but are of little help when various objects are
involved in more complex operations. Ensuring, with lock-based mechanisms, that all
the objects accessed during a complex operation are in a consistent state is difficult and
highly error-prone. Thus, given that in a domain model objects are typically intertwined
in a graph of complex relationships, implementing a concurrent domain model with locks
becomes humanly infeasible.
Fortunately, there is now a more promising approach to deal with the difficulty of
implementing a shared-memory concurrent program: the use of Transactional Memory.
Since the seminal paper on Transactional Memory by Herlihy and Moss [1993] and the
later proposal of Software Transactional Memory (STM) by Shavit and Touitou [1995], this
area of research has developed at an increasingly fast rate during the last few years.
Much of the recent work on STMs addresses the problem of concurrent programming
in languages such as Java by extending the language with the notion of atomic actions, or
transactions [Herlihy, Luchangco, Moir, and Scherer, III., 2003; Harris and Fraser, 2003;
Harris, Marlowe, Peyton-Jones, and Herlihy, 2005], which allow a consistent access to
shared-memory without the use of locks.
Even though many problems remain to be solved in this area, I argue that it is now

58

Versioned Software Transactional Memory

practical to use an STM to develop domain-intensive applications. In fact, I argue that by


doing so we will be able to simplify significantly the development of such applications.
In this chapter, I propose a novel STM that, unlike previous STMs, keeps multiple
versions of each transactional location. This new multi-version STM was conceived with
the development of domain-intensive applications in mind. I present the versioned STM
model independently of a specific programming language, and then describe an implementation in the Java programming language. I argue not only that this new versioned
model is suitable for the development of domain-intensive applications, but also that it is
adequate for an effective and practical implementation that allows its immediate use in
real-world domain-intensive applications.

4.1

Introduction to Software Transactional Memory

The key idea underlying the work on Software Transactional Memory is that programmers
specify which operations should execute atomically, rather than protect accesses to the
data with locks. The intended semantics for such atomic actions is that they execute
atomically, independently of which data is accessed by the operationthat is, that their
execution occurs as if nothing else is executing at the same time.
A simple solution to accomplish the intended semantics for atomic actions is to prohibit parallelism in the application when the atomic action is executing. This is similar
to using a global exclusive lock that all the atomic actions must acquire before executing.
Yet, the goal of having STMs is to allow concurrency, rather than to eliminate it. Thus,
the challenge of an STM is to allow the maximum parallelism in a program while ensuring
that atomic actions execute with their intended semantics.
Even though most of the literature on STMs uses the terms atomic action and transaction interchangeably, in this dissertation I use them with a slightly different meaning: I
use the term atomic action to refer to the definition of a series of operations that should
be executed atomically, whereas the term transaction refers to the execution of an atomic
action. There are many places, however, where this distinction is not relevant, in which
case I will use either of the terms.

4.1.1

Atomic Actions and the Property of Atomicity

The property of atomicity has two distinct aspects, which are of special interest to us.
First, that all the changes performed by the execution of an atomic operation are seen at
the same time by the rest of the system. Second, that either all the changes made by the
operation occur or none occurs; there is no in-between case. The first aspect of atomicity
is what ensures the consistency of the data in the case of concurrent executions. The

4.1 Introduction to Software Transactional Memory

second aspect of atomicity is what ensures the consistency of the data in the case of
failures: Atomic actions give us the ability to recover from failures.
Having atomic actions in the programming language, the correct implementation of
the class Bank from Listing 3.3 on page 35 becomes much easier: We just have to say
that the two methods (transfer and totalBalance) are atomic actions; then, it is
the programming language responsibility to ensure that the methods execute atomically.
With atomic actions, no locks (or synchronized modifiers) are necessary, either in the
class Bank, or in the class Account.
Obviously, this way of programming a concurrent application is much easier than
using lock-based mechanisms. It is easier to specify which actions should execute atomically, than to make sure that all the relevant locks are acquired, in the correct order,
to ensure the exclusive access to a set of data. Furthermore, atomic actions give us the
added bonus of failure recovery.

4.1.2

Read Sets, Write Sets, Commits, and Aborts

Even though the various STMs proposed so far vary considerably in how they ensure the
atomicity property, there are a few concepts that are common to most, if not all, of the
STMs. Two such concepts, which are useful to explain how STMs work, are the read set
and the write set.
From the point of view of an STM, a transaction is a series of operations that either
read values from shared locations or write values to shared locations.1 Thus, the read
set of a transaction is the set of locations read during that transaction. Likewise, the
write set of a transaction is the set of locations written during that transaction.
Given that transactions may occur in parallel, it may happen that two or more of them
access the same shared location. Whereas it may be safe that several transactions read
the same shared location, having one transaction writing to a location that is read by
another would probably break the atomicity property. Thus, it is the job of an STM to
prevent this from happening, usually by keeping track of the read sets and the write sets
of each transaction, and checking for conflicting accesses.
Another important concept regarding STMs is the commit of a transaction. As we
saw, a transaction may execute an arbitrarily large number of operations. Yet, given that
it should execute atomically, none of the values written during the transaction should
become accessible to other transactions until it finishes. The commit of a transaction is
the final operation in a transaction that indicates that all the values written during the
transaction should become accessible to others.
1

The size of a shared location may vary from system to system, from a word of memory to an entire object,
for instance.

59

60

Versioned Software Transactional Memory

It may happen, however, that a transaction must fail, in which case none of the values
written by the transaction should become accessible. In that case, the final operation of
the transaction is an abort, rather than a commit. An abort may be either executed
deliberately during the transaction as part of an atomic action, or issued by the STM
system when it determines that a transaction should not commit successfully.

4.1.3

Transaction Linearizability

The expected correctness condition that a set of successfully committed transactions


must satisfy is that they are linearizable [Herlihy and Wing, 1990]. Informally, a set of
transactions is linearizable if they satisfy the following conditions: (1) each transaction
appears to occur instantaneously at some point in time between its start and its end; and
(2) the execution of non-concurrent transactions preserve their order of invocationthat
is, a transaction that starts only after another ends should never appear to execute before
the latter.
A system that ensures that transactions are linearizable allows us to reason more
easily about our transactional programs, because we know that each transaction sees a
consistent view of the programs data.
Using locks, it is possible, by carefully programming the acquisition of locks at the
beginning of each transaction, to ensure that each transaction will be able to view a
consistent view of the data, because no other transaction will be allowed to change any
of the data that the transaction has locked. This method of concurrency control is often
called pessimistic.
Yet, with STMs the programmer is free of the burden of acquiring locks. So, in contrast
with the previous solution, STMs typically use an optimistic approach to concurrency
control. In the optimistic method, the transaction proceeds without acquiring any locks
and then verifies at some point that the transaction is valid. A consequence of this
method, however, is that a transaction may need to abort because of a conflict with
another transaction.
Typically, a conflict between two concurrent transactions occurs when one of the
transactions needs to read some data that is modified by the other. In such cases, lockbased approaches would probably force one of the transactions to wait for the other,
leading eventually to a deadlock. With most STMs, no wait exists. Instead, the conflict
is detected, and at least one of the transactions restarts. The basic assumption for most
of the STM approaches is that conflicts are rare. Thus, when they occur, it is acceptable
to retry the execution of the transaction, given that the probability of another conflict is
low. If, on the other hand, the probability of a conflict is high, this strategy may lead to
the starvation of some transactions.

4.2 The Rationale for the Versioned STM

To conclude this brief introduction to STMs, consider again the problematic examples shown in Section 3.1: Figure 3.1 on page 33, Figure 3.4 on page 36, Figure 3.5 on
page 36, and Figure 3.6 on page 38. In which way are these examples changed if we use
STMs? Assume that each of the methods executions in these examples corresponds to a
transaction in some STM-based system, and that no other synchronization exists. Then,
most probably, in all of the examples, the two transactions conflict with each other. Note
that in all the examples there is at least an object that is read by one transaction and
modified by the other. The advantage of using STMs in these examples, of course, is that
the conflict is detected and one the transactions is restarted, whereas in the previous case
incorrect results (or deadlocks) occurred.

4.2

The Rationale for the Versioned STM

The goal that I established for the STM proposed in this chapter was that it should be
suitable for implementing domain-intensive applications.
Therefore, I started the design of the STM with a set of assumptions regarding the
characteristics of this type of applications:
That the number of updating transactions (transactions that change any data) is
low when compared with the total number of transactions; probably, less than 10%.
That most of the transactions are medium-sized; that is, that the average size of
their read sets and write sets have hundreds to thousands of objects.
That occasionally there are long-running transactions that need to access thousands to millions of objects.
That an updating transaction may read many objects, but typically changes just a
few.
When I started, most of the research on STMs did not deal well with this kind of
workload. In fact, even now, most of the examples and the benchmarks are for very short
transactions: transactions that access from a couple to a few tens of objects before they
commit.
The problem with larger transactions is twofold. First, the need to keep track of large
read sets and write sets may cause serious performance problems. Second, the probability
of a conflict increases both with the number of objects accessed by a transaction, and
with the transactions duration; this problem becomes more dramatic when we have longrunning transactions that access a significant portion of the object space.
Given this set of assumptions, I established the following set of requirements for the
STM proposed in this dissertation:

61

62

Versioned Software Transactional Memory

The read-only transactions should be made as fast as possible.


Accessing an object for reading should have a low overhead.
The performance of an updating transaction is not very important.
Long-running transactions should be able to execute without halting the entire
system.

In the following section I describe my proposal for an STM that addresses these requirements.

4.3

The Versioned STM Model

In the previous introduction to STMs, I have described in very general terms how the
majority of the existing STMs work. Yet, there are many differences among the various
proposalsfor example, different STMs vary in the number of conflicts they generate, use
different conflict detection algorithms, or differ in where and how they store the read and
write sets.
In this chapter I propose a new model for STMs that is significantly better than the existing approaches in handling particular workloads: Workloads which I argue are typical
of domain-intensive applications.
The distinctive element of my approach to STMs is the use of versioned boxes to hold
the mutable state of a concurrent program. Versioned boxes can be seen as a replacement
for memory locations [Harris and Fraser, 2003] or transactional variables [Harris et al.,
2005].

4.3.1

Model Elements and Terminology

Transactions, as usual, serve to delimit blocks of operations that should execute atomically. A transaction is associated with exactly one threadthe thread that started itand
lasts until that thread either aborts or commits the transaction. A transaction Tc that
starts in the context of another transaction Tp is a nested transaction. Moreover, I say
that Tp is the parent of Tc and that Tc is a child of Tp. Transactions that have no parent
are called top-level transactions.
When a transaction starts, it becomes the threads current transaction until it finishes or another (child) transaction starts. When a transaction finishes, its parent, if any,
becomes the threads current transaction.

4.3 The Versioned STM Model

63

B
2
1
0

87

23

Figure 4.1: Graphical representation of a versioned box. The historys values are
presented in decreasing order of their version number, which is the number at the
lower right corner of each rectangle. At the top of the historys values I put the name
used to refer the box in the text.
Because child transactions are executed by the same thread of their parents, when a
child transaction is executing, the parent transaction is not. Likewise, it is not possible
to have sibling transactions executing simultaneously. In fact, a sibling transaction of
a transaction Tc can start only after Tc finishes. This model of transaction nesting
corresponds to what Moss and Hosking [2005] call linear nesting.
Each transaction has a version number, which is assigned to the transaction when
the transaction starts. This number comes from a global counter that is incremented
only when a top-level transaction that changed some data successfully commits. So, all
the top-level transactions that start between two commits of such transactions have the
same transaction number. When a nested transaction starts, its number is set to the
number of its parent.
During their execution, transactions may access versioned boxes. Unlike conventional
locations, which keep only a single value, a versioned box is a container that keeps a
tagged sequence of valuesthe history of the versioned box. Each of the historys values
corresponds to a change made to the box by a successfully committed top-level transaction
and is tagged with the number of that transaction; this tag number is the values version.
For instance, if a box B is created in transaction number 5, and then changed twice, in
transactions numbered 23 and 87, the history of B will have three values, each tagged
with one of the previous transactions numbers. I represent such a box graphically as
shown in Figure 4.1.
There are two operations that a transaction can execute on a versioned box B:
The read operation, BoxRead(B), which returns the current value of box B in the
transaction.
The write operation, BoxWrite(B, v), which sets the value of box B to v in the
current transaction.
The behavior of these operations and how they interact with transactions will be explained
below. For now, it is important to note that each of these operations must execute in the
context of some transaction. If the executing thread has no current transaction, then

64

Versioned Software Transactional Memory

a new transaction is created immediately before and is committed immediately after the
operation.
I say that a box B is read (respectively, written) in the context of a transaction T, when

T is the current transaction of the thread executing a read (respectively, write) operation
on B.
Finally, besides a reference to its parent, each transaction keeps a record of the
following information: (1) a transaction number, (2) a set of versioned boxes that were
read in the context of the transaction, and (3) a map that maps boxes that were written
in the context of the transaction to the values the boxes were set to. To simplify the
presentation below, given a transaction T, I use the notation T-number, T-readSet,
and T-writeMap, to refer to each of these values, respectively.

4.3.2

Operations on Versioned Boxes

A write operation, BoxWrite(B, v), that executes in the context of a transaction T


does not change the history of B immediately. Instead, it adds a mapping from B to v
into the map T-writeMap. This map corresponds to the private storage of transaction

T, and is used to keep track of the writes performed during T. If the same box is written
more than once during the same transaction T, later values replace earlier values in

T-writeMapthat is, each transaction keeps only the last value written to a box.
A read operation, BoxRead(B), that executes in the context of a transaction T must
return the current value of B. Obviously, the expected behavior for this operation is that it
returns the last value wrote to B in the context of T, if any. To accomplish this behavior,
the read operation searches for an existing mapping for B in T-writeMap. If such a
mapping exists, the corresponding value is returned. Otherwise, the search continues
recursively on Ts parent, until either one mapping is found or no parent exists. If no
mapping is found, then the value to return is obtained from the history of B and B is added
to T-readSet.
The value obtained from the history of B by the read operation executed in T is
the value tagged with the highest number which is less than or equal to the number of

Tfor example, a transaction numbered 30 will read the value 1 from the box B shown
in Figure 4.1 on the preceding page, provided that no value was written to B in the
transaction. As we shall see below when I describe the commit of a transaction, the value
that T reads from B was the last value wrote to B when T started.
I say that a transaction T is a read-only transaction if T-writeMap is empty, regardless of the state of T-readSetthat is, a read-only transaction T is a transaction
during which no box was written, whether some box was read by T or not.

4.3 The Versioned STM Model

Similarly, I say that T is a write-only transaction if T-writeMap is non-empty and

T-readSet is empty. Note that the boxes read during a transaction are added to the
transactions read-set only if they were not written previously in the transaction or in
any of its ancestors; this is one of the distinguishing elements of this model. Thus, a
write-only transaction may actually have read some boxes during its execution, but, if it
did, then it follows that all the boxes read were previously written by the transaction (or
by one of its ancestors).
In the remaining case, when both T-readSet and T-writeMap are non-empty, for
some transaction T, I say that T is a read-write transaction.
Finally, I say that a transaction T is a write transaction when it is either a write-only
or a read-write transaction.

4.3.3

The Transactions Life-Cycle

Transactions start with both their read-set and their write-map empty. Also, when they
start, they get a number that will remain the same during the entire transaction, until
the transaction commits. Only when a transaction commits, and only in certain cases,
can this transaction number be changed to a number greater than the originally assigned
to the transaction.
Intuitively, transaction numbers serve to position the successfully committed top-level
transactions within a serial order of execution. As we shall see, however, the numbers
assigned to each transaction do not unambiguously specify a total order among the transactions. Rather, these numbers specify only a partial order among the transactions, because several transactions may get the same number. I shall discuss how this partial
order is related to the linearizability of top-level transactions in Section 4.3.4. Before that,
however, I shall describe the rest of the transactions life-cycle.
As transactions execute in parallel, accessing shared resources, they may conflict
with one another. Conflicts are detected by examining what is read and written by each
transaction. Other STMs check for conflicts whenever a shared location is accessed during
a transaction. In my model, however, conflicts are detected only at commit time. Until
the transaction commits, it accumulates values in its read-set and write-map, which will
then be used at commit-time to detect conflicts. When a conflict occurs, naturally, the
commit of the transaction fails and the transaction is aborted.
A conflictand, thus, a commit failureoccurs when it is not possible to linearize a
transaction. In principle, a transaction can be linearized at any time between its start and
its end. So, we need to know, for each different kind of transaction, when and whether it
can be linearized.

65

66

Versioned Software Transactional Memory

The use of versioned boxes, with the definition of the read operation given in the
previous section, ensures that all the transactions access a consistent view of the program
state during their execution: After a transaction T starts, and its number is set, it uses
this version number to read the appropriate value of each box; even if several transactions
commit during the execution of T, the changes made by those transactions are not visible
to T. So, ideally, we could linearize each transaction exactly at the time the transaction
starts.
In fact, if T is a read-only transaction, then, by definition, no boxes are changed during
the execution of T. Thus, if T has no effect on the state of the program, we may safely
assume that T executed instantaneously when it startedthat is, that T is linearizable
at the time that it started. Moreover, as nothing is changed by a read-only transaction,
there is nothing to be done in the commit of a read-only transaction.
If, on the other hand, T is a write transaction, then at least one box is changed during
the execution of T. Thus, if T successfully commits, its changes should become visible
after the commit. Because of this, T can be linearized only at its commit-time. To see
why, imagine that T is linearized before its commit and that between that time and the
commit of T a read-only transaction Tr executes. Then, if Tr reads some box written by

T, it should read the value written by T, because it linearizes after T. But, because the
commit of T is not done yet, Tr cannot read that value. Therefore, either the commit of

Tr should fail, or T should be linearized after Tr. As the main design decision underlying
this model is that the commit of read-only transactions never fails, it follows that write
transactions should be linearized at the time of their commits.
Now, the problem that we have is that, even though a write transaction is linearizable
only when it commits, the consistent view of the program state seen by the transaction
corresponds to the program state at the time that the transaction started. Therefore, to
ensure that the transaction can be linearized at its commit time, we have to make sure
that the (already performed) transactions execution is equivalent to the (hypothetical)
transactions execution at commit time. Assuming that the transaction is deterministic,
its execution does not change if the publicly available values read during the transaction
remain the same.
Thus, to ensure that a top-level write transaction T is linearizable, T can commit
successfully if and only if none of the boxes in T-readSet changed after T startedin
this case, I say that T is valid. Note that, by this definition, a write-only transaction is
always valid, because its read-set is empty.
After ensuring that a top-level transaction T is valid, the commit of T proceeds by
renumbering the transaction, so that the new Ts number is one greater than the last successfully committed top-level write transaction. Then, each of the values in T-writeMap
is added to the corresponding history, tagged with this new number. Of course that the

4.3 The Versioned STM Model

check for transaction validity, the renumbering, and the additions to the boxes histories
should be all executed atomically, so that no concurrent commit executes between the
validity check and the end of the commit operation.
The commit of a nested transaction, regardless of its type, is much simpler: it just
needs to propagate the changes made during the nested transaction to the parents context. More formally, when a nested transaction Tc with parent Tp commits, the elements
of Tc-readSet are added to Tp-readSet, and, also, the mappings in Tc-writeMap
are added to Tp-writeMap, overriding any existing mapping in the parents map. Therefore, the commit of a nested transaction always succeeds, regardless of its type.
Finally, aborting either a top-level transaction or a nested transaction simply ends
the transaction and does not have any effect on the existing boxes. All the transactions
values in its write-map, if any, are lost.
A key result for this model is that a transaction T1 may conflict with another transaction T2, only if T1 is a top-level read-write transaction and T2 is an already successfully
committed top-level write transaction. If T1 is a top-level read-only transaction, a toplevel write-only transaction, or a nested transaction, then T1 will never conflict with any
other transaction.

4.3.4

The Linearizability of Top-Level Transactions

In Figure 4.2 on the next page, I present the graphical notation that will be used to
illustrate the execution of transactions. In this figure, I show four top-level transactions,
which all start with number 3. Transaction T1 is a read-only transaction that corresponds
to the execution of the method call o1.m1(); it returns true. Transaction T2 is a
write transaction that commits successfully at instant t1 and it is, therefore, renumbered
with the number 4. Transaction T3 is a write transaction, also, but its commit fails
presumably, because it conflicts with T2. Finally, T4 is a transaction that aborts at
instant t2.
The successfully committed transactions in this simple example are the transactions

T1, with number 3, and transaction T2, with number 4. So, in this case, the transactions
numbers induce a total order on their executions. First we have T1, followed by T2. This
is the only linearization possible in this case.
In general, however, several transactions may have the same transaction number.
When that happens, often there are various possible equivalent linearizations for the
transactions. Nevertheless, in either case, each of the possible linearizations respects
the partial order specified implicitly by the transactions numbers. In fact, given the
partial order induced by the transactions numbers of all the successfully committed toplevel transactions, it is possible to determine all the equivalent linearizations of those

67

68

Versioned Software Transactional Memory

true

T1 o1.m1() 3
T2 3

4
T3 3
T4 3

Time
t1

t2

Figure 4.2: The graphical notation used to represent transactions. This notation is
an extension of the notation introduced in Figure 1.2 on page 8. Each horizontal bar
represents a complete transaction. The number at the beginning of the bar represents
the transaction number when it starts. A bar with a black ending corresponds to a
write transaction, and the black portion of the bar corresponds to the commit of the
transaction. If the commit succeeds, the number set at the end of the bar is the new
number of the transaction, after the commit; a commit failure (because of a conflict)
is represented by a white cross over the end of the black bar. A black cross at the end
of a white bar identifies the abort of the transaction. Additionally, on the left of each
bar, with a gray background, is an id that can be used to refer to the transaction.

transactions.
First, note that the definition of the commit operation forces each write transaction
to have a new number after its commita number which is greater than the number of
any of the previously committed transactions. Thus, it is not possible to have two write
transactions with the same number. Yet, it is possible to have various transactions with
the same number. For instance, in Figure 4.3 on the facing page, I show an example
with eight successfully committed top-level transactions, which have only four distinct
numbers: Transaction T1 has the number 2; Transactions T2 and T7 share the number

3; transactions T4 and T8 are both numbered 5; and transactions T3, T5, and T6 are
all numbered 4. Nevertheless, for each transaction number, there is at most a write
transaction; all the remaining transactions are read-only transactions.
Second, given a read-only transaction Tr and a write transaction Tw with the same
number, it follows from the semantics of the read and the commit operations that the
transaction Tw must be linearized before Tr: Because, if the transaction Tr reads some
box written by Tw, it must read the value written by Tw.
Therefore, from the combination of these two properties, we can derive the first condition that a total order among transactions needs to satisfy to ensure that the transactions
are linearizable: For each set of transactions with the same number, the (only one possible) write transaction must be linearized before all the other transactions; the remaining

4.3 The Versioned STM Model

69

T1 2
T2 2

T3 4
5

T4 2
T5 3

T6 4
T8 5

T7 3
Time
t1

t2

t3

t4

Figure 4.3: Example of 8 successfully committed top-level transactions. Transactions T2, T4, and T5 are write transactions. All the others are read-only transactions.

(read-only) transactions in the set can be linearized in any order.


Moreover, if we require that a transaction with a number N must linearize before any
other transaction with a number greater than N , we have the two necessary conditions to
define all the possible linearizations of a set of transactions.
More formally, given a set of successfully committed top-level transactions, S, all the
total orders on S that respect the following condition represent possible and equivalent
linearizations of Ss transactions:



 

Ti ,Tj S,i ,j n (Ti ) < n (Tj ) n (Ti ) = n (Tj ) w(Ti ) (Ti Tj )
where n (T ) represents the number of the transaction T after the commit, w(T ) is true if
and only if T is a write transaction, and Ti Tj means that Ti appears before Tj in the
total order.
For the example shown in Figure 4.3, the serial order obtained by assuming that
read-only transactions are linearized at the time they start and that write-transactions
are linearized at the end of their commit is:
T1 T2 T7 T5 T3 T6 T4 T8
This serial order is one of the two that satisfies the previous condition. The other equivalent serial order is obtained by swapping the order of T3 and T6 .

70

Versioned Software Transactional Memory

T1 3
T2 3

T3 4

T4 4
Time
t1

t2

t3

A
1

t4

t5

A
2
1

3
2
1

Figure 4.4: Example of unreachable values. The transactions T2 and T4 increment


the value of the box A. At instant t2, the commit of transaction T2 adds a new value,
2, to the history of box A. The value 1, however, does not become unreachable at
instant t2, because transaction T1 is still active. The value 1 becomes unreachable
only at instant t3. Likewise, the value 2 becomes unreachable only at instant t5,
when transaction T4 ends.

4.3.5

Garbage Collection of Old Values

With versioned boxes, old values are not lost; they are kept in the history of the box. This
is necessary so that running transactions can read the correct values even after later
transactions commit new values to a box. But, as old transactions finish, older values
become unreachable and, therefore, may be garbage collected.
To know precisely when old values may be garbage collected, I introduce the notion
of an active transaction. I say that a transaction is active if and only if it is either the
current transaction of some thread or the parent of an active transaction. Moreover, I say
that a value v of a history h is reachable if and only if: (1) v is the most recent value in
the history h, or (2) there is an active transaction T such that
versionOf (v) T-num < versionOf (successor (v))
where the function versionOf returns the version number associated with a value, and
the function successor returns the next value in a boxs history.
Values that are not reachable are unreachable and may be garbage collected. Note
that, once a value v becomes unreachable, no future transaction may make it reachable
again, because all the new transactions must have a version number that is at least equal

4.3 The Versioned STM Model

71

setBalance(150)
getBalance() 100
T1 acc.deposit(50) 3

getBalance() 150

T2 acc.deposit(50) 3

T3 4

getBalance() 100
setBalance(200)
setBalance(150)
Time
t1

acc
100

t2

acc
150
100

t3

acc
200
150
100

Figure 4.5: Parallel deposits on the same account using the STM model based on
versioned boxes. Transaction T2 conflicts with transaction T1, causing the deposit to
be restarted. The reexecution of the deposit operation corresponds to the transaction
T3, which commits successfully.

to the version number of the successor of v.


In Figure 4.4 on the facing page, I show an example that illustrates when old values
become unreachable.

4.3.6

Examples: The Bank Revisited

In Section 3.1, I used the banking domain to illustrate, with some examples, the difficulties
of concurrent object-oriented programming in Java. Now, after presenting my proposal
for an STM model, I shall discuss briefly to what extent this proposal solves the problems
identified.
Because the implementation of the versioned STM model is presented only in the
next chapter, here I discuss the examples at the model level only, by assuming the two
following changes: (1) the balance of each account is stored in a versioned box; and (2)
the methods deposit, withdraw, transfer, and totalBalance are atomicthat is,
the execution of each of these methods occurs within a transaction.
In Figure 4.5, I show the case of two concurrent deposits of an amount of 50 into
the same account acc. Like in the original case (see Figure 3.1 on page 33), when no

72

Versioned Software Transactional Memory

trg.getBalance() 0 src.getBalance() 100


T1 totalBalance() 3

100

T2 transfer(100, src, trg) 3

src.widthdraw(100)
trg.deposit(100)
Time
t1

src
100

t2

src
0

100

trg
0

trg
100
0

Figure 4.6: Execution of the method transfer during the execution of the method
totalBalance using the STM model based on versioned boxes. The use of versioned
boxes ensures that the execution of the method totalBalance can complete successfully with the correct answer, even though transaction T2 commits before the
totalBalance method reads the balance of the account src.

synchronization exists, the two deposits executions proceed in parallel, each one setting
the balance of the account to 150; those executions correspond to transactions T1 and

T2 in this case. But now, one of the depositsthe deposit that corresponds to transaction
T2fails, when the commit of the transaction T2 detects a conflict.
Transaction T2 is a write transaction because it changes the value of the box that
contains the balance of the account acc.2 But, to make the deposit, transaction T2
needs to read the value of the balance before changing it, also. Thus, the balances box
is added to the T2-readSet. Meanwhile, at instant t2, transaction T1 commits and
changes the balance of account acc. So, when T2 commits later, the validity check for

T2 detects that a box in its read-set was changed by other transaction after T2 started.
So, the commit of T2 fails and the deposit operation is restarted. The restart of the
deposit is shown in the figure as transaction T3, which completes successfully at instant
t3, changing the balance of acc to 200, as expected.
2

In Figure 4.5, I make a simplification of the notation. I use the same name acc to denote both the object
of the class Account and that same objects versioned box that keeps the balance. This same simplification
is used in subsequent figures, whenever the meaning is clear from the context.

4.4 Implementation of the Versioned STM Model

The second example that I present here, in Figure 4.6 on the preceding page, shows
the major advantage of an STM model based on multiple versions of data. Whereas
the original version (shown in Figure 3.5 on page 36) produces incorrect results, and
traditional STM models cause a conflict between the two transactions, in the STM that
I propose in this chapter both operations complete successfully with the correct result.
Note that, even though transaction T2 commits before transaction T1 reads the balance
of account src, the changes made by T2 do not discard the old value of any of the boxes
changed by T2; the old values of the boxes are still available, so that T1 can access them.
The old values of the boxes that keep the balance of accounts src and trg become
unreachable only after T1 finishes.
Finally, the example of a failure during the deposit of a transfer operation, as given
in Figure 3.7 on page 39, is trivially solved using an STM: The failure of the deposit causes
the transfer transaction to abort. Given that transactions do not make changes to the
public state of the program until the transaction commits, there is nothing to undo when
the transaction aborts.

4.4

Implementation of the Versioned STM Model

To implement the STM model proposed in the previous chapter, I need to provide answers
for two questions:

How will programmers use the STM in their programs?


What implementing structures do we need to support the model semantics in the
programming language?

Obviously, the answers to these questions are not independent of each otherfor
example, the difficulty of implementing a particular set of constructs influences the choice
of constructs to use to support the model at the language level. Moreover, each of the
questions raises other questions that deserve as much consideration as these ones. For
instance, is the implementation efficient enough to be used for real examples?
In this chapter, I describe an implementation of the STM model based on versioned
boxes that serves as a reference implementation. This implementation is not meant to be
the most efficient, or to be as well integrated as possible with the programming language.
Rather, the implementation I describe here was designed to accomplish a good tradeoff
among the guiding principles described in Section 1.2.2 on page 4.
For instance, regarding the integration of the model with Javathe target programming language of this implementationthe best solution would be, probably, to augment

73

74

Versioned Software Transactional Memory

public class VBox<E> {


public VBox(E initial) { ... }
public E get() { ... }
public void put(E newE) { ... }
}
Listing 4.1: Skeleton of the generic class VBox. Besides the constructor, which
creates a new versioned box, the only public operations for this class are the methods
get and put that implement the read and write operations described in Section 4.3.2,
respectively.

the syntax of Java with new keywords to declare versioned boxes and atomic blocks. This
solution, however, interferes with the tools that programmers use.
Likewise, from a performance-only standpoint, it would be preferable to implement
the support for the STM model at the compiler and virtual-machine level. But, again, this
solution interferes with the tools that programmers use. Furthermore, this solution is
harder to implement than others.
Therefore, the implementation that I describe herethe JVSTMis a pure-Java implementation of the model. Besides the guiding principles already mentioned, the design
of the JVSTM was influenced by the two primary goals that I want to accomplish with
this implementation. First, that the implementation serves as an operational semantics
of the STM model proposed. Second, that it can be used readily and effectively in the
implementation of real examples.
I do not present in this dissertation all the details of the JVSTM implementation. In
particular, I do not show the complete Java source code that implements the JVSTM.3
Rather, I describe the most significant design decisions and skip over the implementation
details that do not add much to the work presented in the remaining of this dissertation.

4.4.1

The JVSTMs API

I start with a description of the JVSTMs API, which is all the information that programmers need to know to be able to use the JVSTM in their programs. One of the major
design decisions regarding the JVSTM was that its interface should be simple and easy
to use.
JVSTM is implemented as a pure-Java library that provides only two visible interfaces
for the programmers that use it.
One of the two interfaces is the public interface of the VBox generic class, which is
shown in skeletal form in Listing 4.1. This class implements the versioned boxes of the
3

The complete source code for the JVSTM is freely available at the JVSTM page [JVSTM].

4.4 Implementation of the Versioned STM Model

public class Transaction {


public static void begin() { ... }
public static void abort() { ... }
public static void commit() { ... }
}
Listing 4.2: Skeleton of the class Transaction. The three methods shown are the
basic operations needed to demarcate transactions when using the JVSTM.

STM model proposed in the previous section: Each instance of this class is a versioned
box, capable of holding a history of values. The method get corresponds to the read
operation, returning the current value of the box. The method put corresponds to the
write operation, changing the value of the box to a new value. Furthermore, according
to the semantics described in Section 4.3.1, if any of these methods is called by a thread
without a current transaction, then the method begins a new transaction, accesses the
box, and commits the just created transactionthat is, the execution of these methods is
atomic. Because the transactions created by these methods are either read-only or writeonly, they never conflict with other transactions. So, the commit of these transactions
never fails.
The class VBox allows us to create and use versioned boxes. Now, we need to be able
to create more complex atomic actions that use these boxes. For that, the JVSTM supplies
the class Transaction, which provides the basic operations shown in Listing 4.2. The
method begin creates a new transaction and makes it the new current transaction for
the executing thread. The new transaction is a top-level transaction if the thread that
calls the method begin has no current transaction; otherwise, it is a nested transaction,
child of the threads current transaction. The method commit tries to commit the current
transaction. If the commit operation detects a conflict between the current transaction
and any previously committed transaction, then the method throws an exceptionan
instance of CommitException, which is a subclass of Javas RuntimeException
and the commit operation fails. The method abort aborts the current transaction, as
expected.
These three operations are the basic building blocks needed to create atomic actions.
But, as we shall see below, often programmers will use simpler constructs to create the
atomic actions that they need in their programs.
To illustrate the basic usage of the JVSTM I show, in Listing 4.3 on the next page, the
JVSTMs version of an HelloWorld program. When this program is executed, it produces
the output shown in Listing 4.4 on the following page. The HelloWorld program creates
a versioned box with an initial value and then changes the box twice, printing the contents
of the box between each change. The first change to the box is made inside a transaction
that aborts. Thus, the change has no effect, and the second value printed is the same as
the first. The second change, however, succeeds, and the third value printed shows the

75

76

Versioned Software Transactional Memory

import jvstm.*;
public class HelloWorld {
public static void main(String[] args) {
// creates a box that holds a string
VBox<String> box = new VBox<String>("Hello world!");
// print the boxs contents
System.out.println(box.get());
// begin transaction to change the box...
Transaction.begin();
box.put("Hi!");
// ...but abort the transaction
Transaction.abort();
// the value did not change
System.out.println(box.get());
// change the box again...
box.put("Hello, again!");
// ...and the new value is printed
System.out.println(box.get());
}
}
Listing 4.3: Complete implementation of JVSTMs version of the HelloWorld program.
This program illustrates the basic usage of the two classes provided by the JVSTM:
The classes VBox and Transaction.

# java HelloWorld
Hello world!
Hello world!
Hello, again!
Listing 4.4: Output produced by the execution of the HelloWorld program. The
second value printed is equals to the first, meaning that the abort of the transaction
undid the write to the box.

4.4 Implementation of the Versioned STM Model

class Account {
final VBox<Long> balance = new VBox<Long>(0L);
...
long getBalance() {
return this.balance.get();
}
void setBalance(long balance) {
this.balance.put(balance);
}
...
}
Listing 4.5: Changes needed in the class Account to use a VBox to hold the
Accounts balance. This listing shows only the parts of the class (shown previously
in Listing 3.1 on page 32) that need changes. Besides changing the field balance
from a long to a VBox<Long>, all the methods that access that field need to be
changed alsoin this case, just the methods getBalance and setBalance.

new value of the box. Note, also, that the calls to the methods get and put do not need
to occur inside a transaction, as explained above.
The program HelloWorld, however, is not a typical example of the programs that use
(or need) the JVSTM; in fact, this program is not even concurrent. Rather, the JVSTM is
meant to be used to create transaction-aware domain objects, which are then manipulated
within atomic actions. For instance, we may reimplement the classes Account and Bank
(originally shown in Listing 3.1 on page 32 and in Listing 3.3 on page 35), to make the
instances of the class Account transactional objects and the methods of the class Bank
atomic.
In general, to use the JVSTM, programmers need to do only two things: (1) use
versioned boxes (instances of the class VBox) to hold all the state of the program that
may change during the programs execution, and (2) use the Transactions methods to
delimit the operations that should execute atomically.
For instance, in Listing 4.5, I show the minimal changes needed to transform the
class Account into a transaction-aware class: We need to replace the classs field by
an instance of the class VBox, and, consequently, replace the expressions that read and
assign to the field by calls to the appropriate get and put methods.
The new class Account, however, is not complete, because the methods deposit
and withdraw should be atomic, or else we may have the problem depicted in Figure 3.1
on page 33. To make these methods atomic, we may use the methods from the class

Transaction, but it is not sufficient to call the method begin on method entry, and to
call the method commit on method exit. Remember that the call to the method commit
can throw a CommitException, because of a conflict. Thus, we need to handle that

77

78

Versioned Software Transactional Memory

class Account {
...
void deposit(long amount) {
while (true) {
Transaction.begin();
boolean txFinished = false;
try {
setBalance(getBalance() + amount);
Transaction.commit();
txFinished = true;
return;
} catch (CommitException ce) {
Transaction.abort();
txFinished = true;
} finally {
// handles other kinds of non-normal termination
if (! txFinished) {
Transaction.abort();
}
}
}
}
...
}
Listing 4.6: Implementation of an atomic version of the method deposit. This implementation uses only the basic begin, commit, and abort operations to implement the intended atomicity semantics. This code handles the case of a transaction
conflict, which makes the method commit to throw a CommitException, by reexecuting the method again. If the methods original body fails for any other reason,
then the transaction aborts.

exception and to restart the execution of the method, as many times as needed, in case
of a conflict.
In Listing 4.6, I show an implementation of the method deposit that implements the
expected atomicity semantics of the STM model, by using the basic JVSTM transaction
operations. The original body of the method is shown in boldface; the rest of the methods
new body is the idiomatic code needed to handle possible transaction conflicts, in which
case the original methods body is reexecuted.4
Because the code needed to implement atomic actions correctly is so verbose, the
JVSTM provides a method annotation to make it simpler: the annotation Atomic. Classes
that use this annotation should be post-processed to wrap the annotated methods bodies
with the necessary Transactions method calls. In Listing 4.7 on the facing page, I
4

In fact, when the original body of the method may change any of the methods parameters, this idiom is
not correct. In that case, we should use an auxiliary method to execute the original methods body.

4.4 Implementation of the Versioned STM Model

class Account {
...
@Atomic void deposit(long amount) {
setBalance(getBalance() + amount);
}
...
}
Listing 4.7: Use of the annotation Atomic to make the method deposit atomic.
This implementation uses only a method annotation to implement the same semantics
of the implementation shown in Listing 4.6. The necessary code to support the
atomicity is introduced by post-processing the byte code.

show an implementation of the method deposit that uses this annotation. Using this
annotation, it is now trivial to make the method withdraw, as well as the Banks methods

transfer and totalBalance, atomic: We just need to annotate each of the methods
with the Atomic annotation.
Finally, note that the execution of an atomic method creates either a nested or a toplevel transaction, depending on whether the method was called during an existing current
transaction or not, respectively. For instance, when the method deposit is called by the
method transfer, its execution creates a nested transaction, because both the method

deposit and the method transfer are atomic. Naturally, when the nested transaction
commits, its changes are merged into its parentthe transfers transaction, in this
caseas expected. This compositional nature of transactions is a fundamental property
to implement rich transactional domain models.

4.4.2

Interaction with the Java Memory Model

The implementation of the JVSTM relies on the revised semantics for the Java Memory
Model that is now part of Java 5.0 [Gosling, Joy, Steele, and Bracha, 2005, Chapter 17].
In this section, I give an overview of the fundamental concepts of this new memory model
that are necessary to understand the implementation of the JVSTM.
The original semantics of the Java memory model was designed to give some safety
guarantees regarding the execution of a multithreaded Java program while allowing, at
the same time, an efficient implementation in multiprocessor machines. This original
semantics of the Java memory model, however, suffered from a series of flaws that made
its use awkward and error-prone [Pugh, 1999]. Thus, to solve these problems, Java 5.0
adopted a new and improved version of the memory model [Manson, Pugh, and Adve,
2005].
The Java memory model specifies how memory operations in a multithreaded Java
program appear to take effect. For instance, to allow for compiler and hardware optimiza-

79

80

Versioned Software Transactional Memory

tions, not every write to a memory location performed by a given thread needs to become
immediately visible to reads occurring in parallel threadsfor example, because the write
operation was reordered with respect to other operations, or because the write was performed to the processor cache which has not been flushed to main memory yet. Thus,
to understand how two or more threads may communicate through shared-memory, it
is crucial to understand the semantics of the memory model. In this aspect, the new
memory model for Java is much more intuitive and easy to use than the original.
The most important guarantees of the Java memory model in what concerns the
implementation of the JVSTM are the following:

Writes to and reads of references and primitive types (other than long and double)
are always atomic. This means that a thread that reads from an int variable, for
instance, will always get a value that was necessarily assigned to that variable, even
if it may not be the last value.
The fields declared as final that are correctly initialized during the construction
of an object are immutable and thread-safe. That is, once the constructor returns,
all the writes to final fields must be visible to all the threads and, thus, it is safe
to access these fields from other threads without synchronization.
Variables declared as volatile provide a synchronization point in a program:
A write to a volatile variable synchronizes with all the subsequent reads of that
variable by any thread. An important consequence of this semantics is that, if a
thread t writes to a volatile variable v, then all the writes made by t (even for normal
variables) before the write to v become visible to any other thread that reads v after
the write made by t.

This semantics of volatile variables is crucial to allow us to guarantee that the changes
made to memory by one thread are consistently seen by the remaining threads. The use of
volatile variables, however, should not be made lightly. Because of their semantics, reads
of and writes to volatile variables increase the cache-coherency traffic in a multiprocessor
system, which may result in a significant performance penalty.

4.4.3

Implementation of Versioned Boxes

To implement versioned boxes, I use a variation of the Handle/Body idiom [Coplien,


1992]. Each versioned box is separated into a handle and a series of bodies. The handle
instancean instance of the class VBoxrepresents the versioned boxs identity, and its
single field is a reference to its most recent committed body. Each bodyan instance
of the class VBoxBodyrepresents a particular version of the boxs state, a value in
the versioned boxs history. The body has fields to represent the boxs value, a version

4.4 Implementation of the Versioned STM Model

81

class VBox<E> {
VBoxBody<E> body;
...
}
class VBoxBody<E> {
final E value;
final int version;
final VBoxBody<E> next;
...
}
Listing 4.8: Structure of the classes VBox and VBoxBody. These classes follow
the Handle/Body idiom to implement versioned boxes. All the VBoxBodys fields are
final to ensure that bodies are immutable and, thus, thread-safe.

B
body:

next:
value:

next:
2

version:

value:

next: null

87

version:

value:
23

version:

Figure 4.7: Structure that represents a versioned box with three values in its history. The box on the left is the VBoxs instance. The three boxes on the right are
the VBoxBodys instances. These objects represent the versioned box B depicted
in Figure 4.1 on page 63.

number, and a reference to the body that maintains the previous value in the boxs
history. Through this reference, the bodies of a versioned box form a linked list, sorted
in descending order of their version number. The version number in each body is the
number of the transaction that committed the body. In Listing 4.8, I show the structure
of these two classes, and, in Figure 4.7, I show the four objects needed to represent a
versioned box with three values in its history (see Figure 4.1 on page 63).
All the fields in the class VBoxBody are final, which makes the bodies immutable
and, therefore, thread-safe. This is essential to ensure that any thread that accesses a
body sees that body properly initialized; in particular, with the correct version number.
The sequence of bodies accessible through a box represent only the values successfully committed to that box. Thus, if a box was written during a transaction, but that
transaction has not committed yet, the value wrote to the box is not in its sequence of
bodies. Rather, the new value is kept in the private memory of the transaction, as we
shall see in the next section. This new value, however, is the current value of the box for
that transaction, and it should be the value returned by a call to the method get, if that
call is made during the same transaction. So, none of the two VBoxs methods, the get
and the put methods, accesses the body of a box directly. Instead, each one delegates
its work to the current transaction.

82

Versioned Software Transactional Memory

class Transaction {
static volatile int lastCommitted = 0;
int number;
Transaction parent;
Map<VBox,VBoxBody> readMap = ...;
Map<VBox,Object> writeMap = ...;
}
Listing 4.9: Some of the fields of the class Transaction.

The method get asks for the current value of the box to the current transaction.
The value returned by the transaction may be one of the values of the boxs history, if
the box was not written yet during the current transaction, or a value that exists only
in the private memory of the transaction, if the box was previously written during that
transaction. The method put simply asks that the transaction records a new value for
the box in the transactions private memory.

4.4.4

Implementation of Transactions

In Listing 4.9, I show some of the fields of the class Transaction. The static field

lastCommitted, which keeps the number of the last committed transaction, works as
a global counter to give an initial number to each new top-level transaction. The value of
this static field changes only when a top-level write transaction successfully commits, in
which case it is incremented by one.
The volatile field lastCommitted works as a synchronization point that affects all
the transactions in the JVSTM. The implementation of the JVSTM ensures that: (1) a
write transaction writes to the lastCommitted field only after it has performed all
the changes in shared-memory that are needed for a commit; and (2) a new transaction
reads of the lastCommitted field before executing any of its shared-memory operations. Given the semantics of volatile variables, this implementation ensures that all the
changes made by the commit of a transaction t become visible to all the transactions
that start after t has written to the field lastCommitted. The (atomic) write to the field

lastCommitted marks, thus, the commit of a transaction.


The field writeMap corresponds to the transactions private storage. The transaction
uses this map to record the new value of each of the boxes written during the transaction.
When, during a transaction, a box is written for the first time, a new entry is added to

writeMap, mapping the box to its new value. If, during the same transaction another
value is written to the same box, then that new value overrides the previous value in the
map. The writeMap does not need to be thread-safe because no other thread can access
this map.

4.4 Implementation of the Versioned STM Model

When a box is read, the transaction consults first its writeMap to see whether a new
value exists for the box. This search is performed recursively in the transactions parent
until either a value is found in some writeMap, or no parent transaction exists. If no
value is found in any of the ancestors writeMaps, then the transaction searches for the
appropriate value in the boxs sequence of committed bodies.
The search for the correct value in the sequence of bodies of a versioned box is a
linear search in that sequence. The value to return corresponds to the body with the
higher version number which is less than or equal to the current transactions number.
Because the sequence of bodies is sorted in descending order of version number, the
search stops once a body with a version number less than or equal to the transactions
number is found. Typically, the search stops at the first element of the sequence; only if
a concurrent transaction committed a new value for the box after the current transaction
started, will the search need to go further in the sequence of bodies.
If the box has already a new value committed by a concurrent transaction, then this
transaction will read an older value, which may cause a conflict for this transaction if
it ever writes to some box and then tries to commit. But, if the current transaction is
a read-only transaction, it may proceed and commit successfully. Thus, the only case
in which we could cause the failure of the current transaction (if we read a box with
newer committed values), is when we know already that the current transaction is a write
transaction. It is not clear, however, that it is worthwhile to perform this test every time
we read a box. If the probability of a conflict is low, then it might be best to skip that test
and detect the conflict only at commit time; this is the approach that I use in the current
implementation of the JVSTM.
The field readMap implements a transactions read set. A transaction inserts a mapping into the readMap, only when a boxs public body is consulted by the transaction,
after trying first all the transactions writeMaps. In this case, the transaction records in
the readMap that the box was read, and what was the body read for that box.
In Figure 4.8 on the next page, I show an example of one box, identified as box A,
and two transactions, T1 and T2, that access the box A. Note that two new values exist
for box A: one in each of the transactions writeMap. Nevertheless, these values are not
accessible through the box, because neither of the transactions committed yet. Thus,
these values are only visible in their corresponding transactions.
The values stored in the writeMap of a transaction are added to the sequence of
bodies of their corresponding boxes, only when the transaction commits, and only if the
transaction is valid. In Figure 4.9 on page 85, I show what happens if transaction T2
commits. Transaction T2 is valid, because it is a write-only transaction (its readMap is
empty). So, the commit of the transaction proceeds by renumbering the transaction (from

4 to 5), and by committing a new body for each of the entries in the writeMap (in this

83

84

Versioned Software Transactional Memory

A
body:

T1
number:

next: null
value:

readMap:

100

version:

writeMap:

T2
number:

150

readMap: empty
writeMap:

Figure 4.8: Values stored in the Transactions fields readMap and writeMap.
The box A contains only one body in its history, with the value 100. That value was
read by transaction T1 before the new value 150 was written to the box. Because the
readMap of transaction T2 is empty, either T2 never read the box A, or the read was
done only after writing the value 0 into it.

case, only one).


To commit a new body for an entry in the writeMap, the transaction creates a new
body (initializing it with the correct values) and then replaces the current body of the
entrys box by this new body. In the case shown in the figure, the transaction T2 creates
a new body with the value 0, the version 5, and pointing to the current body of box A; then,
it sets the body of A to this new body. Note that, given that the bodies are immutable,
when the new body replaces the current body of the box A, it is already properly initialized
and may be accessed by other threads, which will see the final values in the slots of the
body.
After the commit of transaction T2, however, the transaction T1 is not able to commit
anymore. To commit transaction T1, we need to check first whether T1 is valid. The
validity check verifies that all the bodies in the readMap correspond, still, to the most
recent body of their corresponding box. Thus, because the current body of box A is not
the body read by T1, the check fails. Note that, if the transaction T1 committed before
transaction T2, then the commit of T2 would still succeed, because T2 has not read
anything.
This discussion of a transactions commit applies only to top-level transactions. The
commit of a nested transaction is much simpler, because it only needs to propagate the
contents of both the readMap and the writeMap to the transactions parent. The JVSTM
implements this by adding all the entries of the childs maps into the corresponding maps
of the parent.

4.4 Implementation of the Versioned STM Model

85

A
body:

T1
number:

next: null
value:

readMap:

100

version:

writeMap:

150

next:

T2
number:

value:

readMap: empty
writeMap:

version:

0
5

Figure 4.9: Final result after transaction T2 commits. The cross on the link between
the field body of box A and its original body represents the fact that this link no longer
exists. Instead, the field body now points to the new body created by transaction T2,
which is shown on the bottom-right corner of the figure. The structures of transaction
T2 are shown in gray to represent the fact that the transaction finished.

4.4.5

Atomic Commits

The commit of a top-level write transaction is the only place in the JVSTM where the
shared state of the program is changed. Thus, at least during such commits there must
be some form of synchronization. In fact, as we saw in Section 4.3.3, the commit of a
write transaction must execute atomically. But, what does this need for synchronization
entails? And to which extent do we need synchronization between the various JVSTMs
transactions?
The careful use of the volatile field lastCommitted, as described in the previous section, allows that transactions proceed without any other form of synchronization during
their entire execution, up to (but excluding) the commit operation. In fact, even when a
transaction is committing new bodies for the boxes that it wrote to, the remaining transactions may continue to access those boxes without any kind of synchronization; not even
some form of volatile-like memory barrier, given that the field of a box is not volatile.
To see why, consider what may happen if a transaction t1 is committing a new body for
a box b while another transaction t2 is reading that boxs history to access the box value.
Given that writes to and reads of references in Java are atomic, either the transaction t2
gets the new body for box b, or the old one; there is no other alternative. The value of b
in t2 should be the old value, given that t2 started before t1 finished its commit. So, the
problem, if it exists, is when t2 gets the new body for b. If that happens, however, t2 will
skip over that body and access the old body either way because the version of the new
body is necessarily higher than the version of t2. Also, the access of t2 to the new body
is perfectly safe because bodies are immutable and, therefore, thread-safe.

86

Versioned Software Transactional Memory

T1 2

commit()
T2 2

commit()
5

T3 2

commit()
6

T4 2
T5 2

commit()
Time
t1

t2

t3

t4

t5

Figure 4.10: Several write transactions committing at the same time. This figure
shows that, if multiple top-level write transactions try to commit at the same time,
then all but one of the transactions must wait until it gets hold of the exclusive
commit lock.

This lack of synchronization during the execution of a transaction extends farther


in the case of read-only transactions, because their commit is essentially void. So, an
important property of the JVSTM implementation is that the execution of transactions,
when restricted to read-only transactions, is wait-free [Herlihy, 1991]. In fact, even though
the system as a whole is not wait-free when we consider also write transactions, all readonly transactions satisfy the wait-freedom property that they make progress in a finite
number of steps.
The synchronization of commits for top-level write transactions, however, is crucial
to ensure that transactions are linearizable. We cannot allow thatafter checking that
a transaction is valid, but before the commit finishesany of the boxes read by the
transaction is modified in any way by a parallel commit. In my current implementation of
the JVSTM, this is accomplished by forcing all the commits of top-level write transactions
to synchronize on a single object. Thus, the commit of top-level write transactions execute
sequentially, as shown in Figure 4.10.
Clearly, as Figure 4.10 illustrates, this solution may not scale well for machines with
many processors and workloads with a high percentage of write transactions. Yet, I
chose this solution for the current implementation of the JVSTM not only because of its
simplicity, but also because it fits well with the assumptions underlying the design of
the JVSTM; in particular, that there are many more reads than writes, and that write

4.4 Implementation of the Versioned STM Model

transactions change a small number of objects.


Nevertheless, I emphasize that neither the execution of concurrent transactions (other
than their commit), nor the commit of read-only transactions are affected by this implementation choice.
There are, however, alternatives to this simple implementation that may increase the
parallelism of the commits. For instance, rather than acquiring a single exclusive lock
for the entire commit, use read-write locks per object and acquire, in a well-defined total
order, read locks for each of the boxes in the readMap, and write locks for each of the
boxes in the writeMap. Then, while holding the locks, proceed with the commit, and, at
the end of the commit, release all the locks.
The problem with this alternative is that it may well be that the overhead of managing
the locks offsets the gains of the increased parallelism, at least for the currently available
machines with a limited number of processors.
A better alternative may be to continue to execute the commits one after another,
but execute each commit in parallel. The commit of a transaction needs to validate each
element of the transactions read set and then commit new bodies for each of the boxes
in the transactions write set. Obviously, each of these operations may be performed in
parallel. So, the key idea here is that a committing transaction puts itself into a queue of
commit operations and then continuously helps the first operation in the queue by doing
part of that operations commit, until its own commit is processed.
By using well-known lock-free implementations of queues, it becomes relatively simple
to implement such a scheme for the commit of write transactions, which would make the
JVSTM lock-free. In fact, I made some experiments with such an implementation, but
the overall performance of the JVSTM in my benchmarks was much lower than with the
single-lock solution described above. Yet, given the limited extent of my experiment, the
results obtained were only anecdotal and far from conclusive.

4.4.6

Speculative Read-Only Transactions

The implementation of transactions described above registers all the boxes that are read
during the transaction in the transactions readMap . This recorded information is necessary to validate the top-level write transactions when they commit. But, for read-only
transactions, all this registering is useless work, and, worse, constitutes a significant
overhead, both in memory, to store all the maps entries, and in time, to update that
map. Moreover, when we read a box, the transaction searches through the hierarchy of
transactions for an existing body in any of the hierarchys writeMaps, again introducing
a significant overhead. Therefore, I would like to eliminate these overheads for the common case of read-only transactions. Unfortunately, we do not know beforehand whether

87

88

Versioned Software Transactional Memory

Transaction

ReadWriteTransaction

NestedTransaction

ReadTransaction

TopLevelTransaction

Figure 4.11: The hierarchy of transactions implemented by the JVSTM.

a transaction is read-only or not.


We can, however, speculatively assume that a top-level transaction is read-only when
it starts. If, contrary to this assumption, a write operation is attempted during the execution of the transaction, then we must abort and restart the transaction as a generic
read-write transaction. In programs where read-only transactions largely outnumber
read-write transactions, the cost of restarting the erroneously assumed read-only transactions may be much less than the cost of unnecessarily using read-write transactions.
One way to improve on this strategy is to give to the programmer the possibility of giving
hints about the nature of a transaction. Another, is to adaptively change the nature of a
transaction in runtime based on prior executions of the same atomic action.
This speculative strategy can be applied to nested transactions as well, but requires
some special considerations. If the parent transaction is not read-only, the nested transaction still has to register all the boxes read, so that it can add them to the parent at
commit time. Therefore, we can use a nested read-only transaction only when the parent
is a read-only transaction. Moreover, when a write operation is executed in a nested
read-only transaction we should restart the top-level transaction, rather than the nested
transaction itself.
To implement these different kinds of transactions, the JVSTM provides several subclasses of the class Transaction, as shown in Figure 4.11.

4.4.7

Implementation of Garbage Collection

To conclude the discussion of the most relevant aspects of the JVSTM implementation, I
describe in this section how the JVSTM implements the garbage collection of old values

4.4 Implementation of the Versioned STM Model

from the boxes histories. As we saw in Section 4.3.5, old values are needed only as long
as there are active transactions that may need to access them. Thus, when an old value
becomes unreachable, we can discard that value from the boxs historythat is, from the
sequence of bodies.
A corollary of the definition of unreachable values is that, if a boxs history value is
unreachable, then all the previous versions in that same history are unreachable, also.
Therefore, to discard an unreachable value, we may simply trim the tail of the sequence of
bodies, from the point where the unreachable value occurs forward. This is accomplished
by setting the field next of the preceding body to null: After this trimming, all the

VBoxBodys objects that were in the tail of the body that was trimmed become garbage
collectable by the Java runtime. In the following, I refer to this trimming process as the
cleaning of a body.
Now that we know how to garbage collect unreachable values, we just need to know
how to find those unreachable values, and when to run the garbage collection process.
Starting with the latter problem of knowing when to run the garbage collection process,
it follows from the definition of an unreachable value that values may become unreachable
only when a top-level transaction finishes (either with a commit or an abort). Thus, in the
JVSTM, the cleaning of unreachable values occurs as part of the finishing of a top-level
transaction: When a top-level transaction finishes, it checks whether its finishing has
made some values unreachable and, if that happens, it cleans those values.
Yet, the finishing of a top-level transaction makes some values unreachable only if
certain conditions are met. Namely, assuming that the finishing transaction is T, both
of these conditions must hold: (1) the current value of the lastCommitted counter is
greater than T-num, and (2) there is no other active transaction with a number less than
or equal to T-num.
If the first condition holds, then we know that all the future transactions will have
a number greater than T-num. Moreover, if the second condition holds, it means that
the finishing transaction is the last active transaction with the number T-num. So, after
the finishing of the transaction, some values will necessarily become unreachable. The
question now is to find which values become unreachable.
The obvious brute force solution to this problem is to sweep all the boxes, cleaning
up any unreachable values found. Yet, this solution forces us to keep a list of all the
boxes in the program. Moreover, it is unnecessarily inefficient, because most of the boxes
do not need any sweeping at all: For instance, boxes with only one value do not have
unreachable values. So, an improvement over the previous strategy is to use a list of
boxes which are candidates for sweeping. We put boxes into this list when they get a new
value, and take them out of the list when they are cleaned up (if they end up with only
one value). Nevertheless, this strategy may still examine too many unneeded boxes.

89

90

Versioned Software Transactional Memory

The key observation that allows the JVSTM to implement a more efficient garbage
collection process is the following: All the boxes written by one transaction need cleaning
exactly at the same time. To see why, consider that the transaction Ti writes new values
for the boxes B1 , B2 , . . . , Bn . Then, when Ti commits, each of the boxes B1 through Bn will
have a new value tagged with the version number assigned to Ti by the commit operation.
Assume that this number is i. Thus, the previous value of each of the boxes B1 through Bn
becomes unreachable when all the active transactions with a number less than i finish
that is, the previous values become unreachable all at the same time, because the new
value of each box was created at the same time, also.
This answers our question of knowing which values become unreachable when the
finishing of a transaction T triggers the garbage collection process: We need to clean
up, at least, all the bodies committed by the transaction T-num + 1. I say at least,
because the finishing of a transaction T may, in fact, cause that various other values
become unreachable. Consider that, while T was running, n top-level write transactions
committed successfully. Then, the finishing of T may cause that all the values that were
updated by the commits of all those n transactions become unreachable.
Therefore, the JVSTM implements the garbage collection of unreachable values by
keeping track of which transactions are active at a given instant and which boxes need
cleaning for a given transaction number. The difficulty of implementing this approach is
in doing it without introducing two much synchronization overheads in the bookkeeping
of this information. We need synchronization for this bookkeeping because transactions
start and finish concurrently.
The JVSTM uses the class ActiveTxRecord to keep track of all the bookkeeping
information necessary for the garbage collection of unreachable values. The fields of an

ActiveTxRecord, shown in Listing 4.10, hold the following information:

A transaction number, in the field txNumber . The field txNumber is initialized


when the record is created and never changes thereafter. Its value identifies to
which transaction number pertains the information of the record.
A list of bodies to clean, in the field bodiesToClean. This list contains all the bodies
that were created by the commit of the write transaction with number txNumber.
These are the bodies that need cleaning when we know that there is no active
transaction with a number less than txNumber. When the bodies get cleaned up,
this field is set to null.
A count of the active transactions, in the field running. The field running is atomically incremented when a new transaction with number txNumber starts, and is
atomically decremented when that transaction finishes. So, when the value of this
field is zero, we know that there are no active transactions with number txNumber.

4.4 Implementation of the Versioned STM Model

class ActiveTxRecord {
final int txNumber;
final AtomicReference<List<VBoxBody>> bodiesToClean;
final AtomicInteger running = new AtomicInteger(1);
volatile ActiveTxRecord next = null;
...
}
Listing 4.10: The fields of the class ActiveTxRecord. This class uses the atomic
variables provided by the standard java.util.concurrent.atomic package.

There may, however, be active transactions with a lower number; so, it is not sufficient to look at the value of this field to know when to clean up unreachable values.
A reference to the next record, in the field next. The field next starts with the null
value when the record is created, and is assigned at most once, when a new write
transaction commits and creates a new record, in which case this field is assigned
to that new record. So, this field creates a linked list of records, with older records
pointing to newer records.

The key idea underlying the JVSTM implementation is to keep track of the count of
active transactions for each possible transaction number and, when that count reaches
zero, to clean up the bodies of the next transaction record that point, now, to unreachable
values. To accomplish this, the JVSTM updates this bookkeeping data structure in each
of the following cases:

When a new top-level transaction starts, in which case we must increment the

running count on the record that corresponds to the starting transactions number.
When a top-level transaction finishes (either after commit, or on abort), in which
case we must decrement the running count on the record that corresponds to the
finishing transactions number.
When a top-level write transaction commits, in which case we create a new record
and link it to the most recent one. When a top-level write transaction commits, it
creates a new record with the new number of the transaction and with the list of
bodies committed by the transaction. The running count starts with the value 1
because there is one transaction running: the transaction that is committing. When
later in the commit process the transaction finishes, it decrements the running
count as any other finishing transaction.

The condition that triggers the cleaning process when a top-level transaction finishes is
reaching a combination of values for three of the records fields: (1) the field running has

91

92

Versioned Software Transactional Memory

a count of zero, which means that no other transaction with the same number is running;
(2) the field next has a non-null value, which means that all the future transactions will
have a number greater than the currently finishing; and (3) the field bodiesToClean
has a null value, which means that this record was already cleaned up and, thus, there
is no older transaction running, also.
As these values are all read and modified concurrently as part of the starting and the
finishing of transactions, we need to ensure that no data races exist, or that, if they exist,
they are safe. One way to guarantee the safety of these processes is to use locks to protect
the access to these fields in the critical regions. This solution, however, entails too much
synchronization. So, the JVSTM implements a lock-free alternative instead.
The lock-free algorithm that the JVSTM implements uses the atomic variables provided
by the package java.util.concurrent.atomic, which is part of the standard Java
libraries. In particular, the JVSTM implementation depends on the compare-and-swap
operations provided by these atomic variables to detect and recover from eventual data
races.
The highlights of the implementation are the following:

When a new top-level transaction starts it needs to get a transaction number and
to increment the corresponding records running count. Therefore, it starts with
the most recent record and speculatively increments its running count. By doing
so, it prevents that other transactions clean up this record (if the record has not
been cleaned up already). Then, it must check whether the next field is nonnull. If it is, then there is already a more recent transaction number. As each
new transaction must start with the newest transaction number, it backs off of its
speculative increment by decrementing the running count again (which may trigger
the cleaning process, given that the field next is non-null), and tries again with the
next record. The implementation of this operation is shown in Listing 4.11. This
algorithm may cause starvation on a thread that is trying to start a new transaction,
but only if successive write transactions commit in between. Thus, this algorithm
is lock-free, rather than wait-free.
When a top-level transaction finishes, it decrements the running count of the
record that corresponds to its number. If the count reaches zero, it needs to check
whether it needs to clean up the next record. So, it checks if the field next is
null; if it is, then there is nothing to clean up yet. If, on the other hand, the value
of next is non-null, then no new transactions may start for this record (given
the implementation described in the previous point) and it may have to clean the
next record. To see whether this is true, it checks again if the running count is
zero5 and if this record was cleaned up already, cleaning up the next record if both
5

It may not be, because between the first check and the check for the next field, one transaction may

4.4 Implementation of the Versioned STM Model

class ActiveTxRecord {
// returns the number to give to the new transaction
int startTransaction() {
ActiveTxRecord rec = this;
while (true) {
rec.running.incrementAndGet();
if (rec.next == null) {
// if there is no next yet, then its because the rec
// is the most recent one and we may return its number
return rec.txNumber;
} else {
// a more recent record exists, so backoff
rec.decrementRunning();
// and try again with the new one
rec = rec.next;
}
}
}
}
Listing 4.11: The operation used during the start of a new transaction to find the
transactions number.

conditions are true. Finally, if the transaction cleaned up the next record, it should
see whether it needs to propagate the cleaning to the record following it. This is
needed because a late finishing transaction may be responsible for cleaning up a
series of records that were all waiting for its finish. The implementation of this
operation is shown in Listing 4.12.
Finally, the commit of a top-level write transaction is easier to deal with. When a
top-level write transaction commits, it creates a new record and sets the field next
of the most recent record to the newly created record. This change occurs within
the mutual-exclusion lock used for the commit operation and, so, is thread-safe
with regard to other commits; also, given that the field next is volatile, this change
synchronizes with any other thread reading the same field. Furthermore, changing
the field next in the commit of a transaction cannot trigger the cleaning process,
because the committing transaction is still running and its number is either equal
to the record that will see its next field changed (in which case, that record must
have a running count greater than 0), or it belongs to an older record (in which
case, the record with the next field updated is not cleaned up). Yet, after changing
the next field, the commit operation decrements the running count for its old
number, which may trigger now the cleaning process.

have started, thereby incrementing the count, and another transaction may have committed, setting the
next field to a non-null value.

93

94

Versioned Software Transactional Memory

class ActiveTxRecord {
void finishTransaction() {
if (running.decrementAndGet() == 0) {
// when running reachs 0 maybe
// it is time to clean our successor
ActiveTxRecord rec = this;
while (true) {
// it is crucial that we test the next field first,
// because only after having the next non-null,
// do we have the guarantee that no transactions
// may start for this record
if ((rec.next != null)
&& (rec.bodiesToClean.get() == null)
&& (rec.running.get() == 0)) {
if (rec.next.clean()) {
// if we cleaned up, move to the next
rec = rec.next;
// and repeat the test
continue;
}
}
break;
}
}
}
boolean clean() {
List<VBoxBody> toClean = bodiesToClean.getAndSet(null);
//
//
//
//
if

the toClean may be null because more


than one thread may race into this method
yet, because of the atomic getAndSet above,
only one will actually clean the bodies
(toClean != null) {
for (VBoxBody body : toClean) {
body.clearNext();
}
return true;
} else {
return false;
}
}
}
Listing 4.12: The operation used when a transaction finishes to clean up unreachable values. The method clean is an auxiliary method that cleans up each of the
bodies.

4.5 Related Work

4.5

Related Work

Since the seminal paper on Transactional Memory from Herlihy and Moss [1993], and the
later proposal of a software realization of the same idea by Shavit and Touitou [1995], the
research on Software Transactional Memory remained mostly dormant until 2003, when
a couple of influential papers spurred again the interest in the area [Herlihy et al., 2003;
Harris and Fraser, 2003].
Unlike the original STM from Shavit and Touitou, which was lock-free, both the DSTM
proposed by Herlihy et al. [2003] and the STM from Harris and Fraser [2003] are based on
a weaker non-blocking guarantee: obstruction-freedom. But, by being obstruction-free,
these STMs are also simpler and more efficient.
Since then, the research on Software Transactional Memory has been immensely
active, with many researchers proposing new STM implementations. As a consequence
of all this activity, there is now a large number of STM proposals, covering a significant
portion of the design space. In [Marathe and Scott, 2004; Marathe, Scherer, and Scott,
2004], Marathe and colleagues make an initial comparison of the then existing approaches
to STM, but many other proposals exploring other options were made since then.
The versioned STM that I propose in this dissertation, originally presented in [Cachopo
and Rito-Silva, 2005, 2006], was the first STM to propose the use of multiple versions
for each transactional location. By using multiple versions, this STM is better suited for
long-running read-only transactions than the rest. This comes at the expense of more
memory overheads.
The use of multiple versions to increase the concurrency of transactional systems is
well known in the area of database management systems. Since the seminal work of
Bernstein and Goodman [Bernstein and Goodman, 1983] on multi-version concurrency
control, the technique of using multiple versions as the basis for optimistic concurrency
control was applied in several contexts. One such application was made by Graham and
Barker [Graham and Barker, 1994]. They presented a formalism for describing multiversion object base systems which is similar, at the model level, to my proposal. However,
their work is in the context of object databases, which have different concerns compared
to a programming language level software transactional memory.
The idea of keeping a history of values for each object whenever it is changed, rather
than replacing the old value, was used by Reed in the context of the distributed execution
of atomic actions [Reed, 1978, 1983]. However, many of his concerns regarding the
difficulty of synchronization on a distributed system do not apply in the context of STM.
Moreover, in Reeds approach, when an object is read, the history of the object may be
updated by that read operation, thereby introducing a point of synchronization among
concurrent read-only transactions, which defeats somehow the benefits of this approach.

95

96

Versioned Software Transactional Memory

A notable aspect of my implementation is that it uses a single lock to ensure mutual


exclusion during the commit, whereas otherse.g. the DSTM from Herlihy et al.rely on
a single CAS operation to perform the commit, because all the values are put in place
during the transactions execution. My approach is simpler and allows for a natural
implementation of nested transactions, which the DSTM and other STMs based on the
DSTM design do not support. The problem with my approach, of course, is that it may
cause scalability problems if the number of write transactions and the number of available
processors rise significantly. Yet, there is nothing in the versioned model that prevents a
non-blocking implementation of the commit operation, as I discussed in Section 4.4.5.
The support for nested transactions is an important feature in an STM that aims to be
usable for implementing a rich domain model, because otherwise it becomes very difficult
to make composition of atomic actions. The paper on Composable Memory Transactions
by Harris et al. [2005] is well-known for discussing this problem.
The semantics of nesting in my STM corresponds to what Moss and Hosking [2005]
describe as linear nesting. This simplified model allows for a simpler implementation of
nesting and is sufficient to support the retry and the orElse operations from Harris
et al. [2005]. Although I did not address these operations in this dissertation, their
implementation in my model is quite straightforward.
Two important goals for my STM implementation were that it should be easy to learn
and use, and that it allowed programmers to use it without having to change their tools.
So, I implemented the JVSTM as a pure Java library with a minimal interface. Most of
the work on STMs, however, does not share this concern for ease of use. In fact, in many
cases, either the STM was implemented as an extension of the language (interfering with
the tools) or as a library that has an awkward interface. More recently, however, Herlihy,
Luchangco, and Moir [2006] proposed a new version of their original DSTM, the DSTM2,
that has a simpler programming interface based on transactional factories.
Finally, another STM approach based on multi-versions was proposed recently by
Riegel, Fetzer, and Felber in [Riegel et al., 2006b; Riegel, Felber, and Fetzer, 2006a].
This STM borrows much of its implementation from the original DSTM design. Like
the DSTM, it uses an intermediate locator object to keep track of the changes made to
each transactional object. Yet, whereas the DSTM keeps at most two versions of an
object in its locator (the new and the old state of the object), the SI-STM proposed by
Riegel et al. keeps track of multiple versions. Unlike the JVSTM, the SI-STM does not
support nested transactions, but shares with the JVSTM the fact that it may access a
consistent view of the data by reading older versions of the objects, if necessary. Thus, it
performs reasonably better than the non-versioned STMs for workloads with long-running
transactions.

4.6 Summary

4.6

Summary

This chapter describes a new Software Transactional Memory (STM) that, unlike other
STMs, uses multi-version transactional locations called versioned boxes.
The chapter starts with a brief introduction to some fundamental concepts of STMs
and then describes the rationale underlying the development of this new STM: To create an
STM suitable for the implementation of domain-intensive applications. More specifically,
to create an STM that handles well workloads that have a high read/write ratio and that
are composed mostly by medium to large transactions.
After describing the rationale, it describes the proposed Versioned STM model and
gives a detailed description of the models implementation in the Java programming language. The proposed STM model ensures the linearizability of a set of successful transactions, and supports (linear) nested transactions. Moreover, the model defines in which
conditions may a garbage collector free the old versions of each box.
The implementation describedthe JVSTMis a full-fledged implementation of the
STM model that provides a simple and easy to use API, thereby facilitating its immediate
adoption for the implementation of rich domain models. Moreover, the implementation
of the JVSTM shows that the versioned STM model is amenable to simple and efficient
implementations.
The chapter describes, also, a lock-free algorithm for keeping track of old values so
that they may be garbage collected when they become unreachable.
Finally, the distinctive features of this new STM model and its implementation are that
read-only transactions never need to synchronize with any other transactions, and that,
also, read-only transactions have the guarantee of being able to complete successfully.

97

98

Versioned Software Transactional Memory

Chapter 5

Domain Modeling Language


At least since the introduction of the Chen Entity-Relationship Model [Chen, 1976], well
before object-oriented programming became mainstream, relations are an integral part
of domain modeling languages. Therefore, we would expect that recent programming
languages should have constructs to facilitate the implementation of relations. In fact,
about twenty years ago, Rumbaugh argued convincingly for the inclusion of relations as
a primitive declarative construct in object-oriented programming languages:
Object-oriented languages express classification (the grouping of objects into classes)
and generalization (the refinement of classes into subclasses) well, but do not contain syntax or semantics to express relations directly. Any program can implement
particular relations on an ad hoc basis, but the abstraction may get lost in the implementation mechanisms.
[...]
Object-oriented languages have built-in constructs for generalization because it is
a natural concept that people use in ordinary discourse; it allows algorithms to be
written more concisely and more clearly; and it is common enough to justify building
it into a language. Relations are also natural, productive, and common in abstracting applications. An object-oriented language is more expressive if relations are a
primitive declarative construct, on the same footing as classes.
[Rumbaugh, 1987]

And yet, surprisingly, none of the more recently developed object-oriented programming languages provide such a construct. Unfortunately, the consequence of this oversight in the design of new programming languages is an undesirable burden for the
programmers that need to implement domain models, as we have seen in Section 3.3.
Thus, given the current state of the object-oriented programming languages, a possible
approach to simplify the implementation of domain models is to extend those languages
with a set of new constructs for implementing relations. This solution, however, has the
inconvenient that interferes with the development tools that programmers use and goes,

100

Domain Modeling Language

therefore, against the guiding-principles that I established in Section 1.2.2.


The approach that I propose in this chapter follows a different route. Rather than
extending a programming language, I propose a new languagethe Domain Modeling
Language (DML)that complements and integrates with the Java programming language.
The DML language is a micro-language designed specifically to implement the structure
of a domain model: It has constructs for specifying both entity types and associations
between entity types. Associations are the primitive declarative construct for relations
that was argued for by Rumbaugh [1987].
I start with the rationale underlying the development of this new language, and then
I describe the language in detail, showing both its syntax and how it integrates with the
Java language. I discuss related work at the end of the chapter, in Section 5.8.

5.1

DMLs Rationale

The objects of a domain model have special needs, compared to the remaining objects of
an application, as we have discussed already in Section 2.1.4 and Section 2.1.5. These
special needs may include, for example, that they are stored in a database whenever they
change, or that they be accessed by multiple concurrent threads in a consistent way.
Most often, because of these needs, the classes that represent the domain objects must
follow some coding conventions, to ensure that their instances behave as expected. For
instance, if we are using the JVSTM described in Section 4.4 to make the domain objects
transactional, we must use instances of the class VBox for all the mutable fields of a
domain class.
In some cases, as when using the JVSTM, the code conventions are simple to follow.
But, as simple as they may be, they still must be applied manually by the programmers,
introducing unnecessary effort into the programming task, and opening the possibility of
errors. Thus, the idea of automating these programming tasks was the first incentive that
led me to the development of the DML: I wanted a simple way to specify that a class is
a domain class, and to have this class transformed automatically into a class that uses
versioned boxes for all its fields.
Nevertheless, this reason alone is not sufficient to justify the need of a new language.
In fact, transforming each of a classs field into a field of another type and changing the
code that accesses that field to use a different access expression, is well within the reaches
of Javas annotations and post-processing technologies.
The most important reason for the development of the DML language, however, was the
lack of support in Java for the implementation of associations between classes. To solve
this problem in a convenient way, I would need to extend the syntax of the Java language

5.1 DMLs Rationale

with new top-level syntactic constructs to represent associations. Unfortunately, the


extension mechanisms available in Java do not allow syntax extensions. The top-level
constructs of a Java program are the class and the interface, and no provisions exist to
create other syntactic constructs. So, I decided to create a new languagethe DMLwith
the appropriate constructs to represent both the classes and the associations of a domain
model.
Yet, it is not the goal of DML to replace Java. Rather, it should integrate with Java,
so that programmers can still leverage on all the advantages of the Java programming
language. The key idea is that this new language should be as small as possible, by
providing constructs to represent a domain models structure, but leave all the rest to
Java. In particular, the behavior of the domain models classes should be programmed in
Java, as before. Furthermore, the code that implements the classes behavior should be
the same, regardless of how the class structure is developed (either with DML or in plain
Java).
Therefore, DML is designed to represent only the structural aspects of a domain model,
using, for that, constructs that are as close as possible to the constructs used at the modeling levelconstructs such as class and association. But also, because the domain model
is not complete without its behavior, which is programmed in Java, the models structure expressed in DML must integrate with the Java code that implements the models
behavior. Both the syntax and the semantics of DML reflect this requirement of seamless
integration.
As the DML is designed to target specifically Java programmers, its syntax borrows
from the syntax of Java whenever possible. Moreover, the semantics of DML is specified
by describing the set of Java classes that result from a DML domain specification: To
implement the domain models behavior, programmers must know the interfaces of these
resulting classes. Thus, implicit to the semantics of DML is that there is a compilation
process that transforms a DML domain specification into a set of Java classes.
Finally, the last requirement that influences the design of DML is that, even though
programmers must know the interfaces of the classes generated by the DML compiler,
they should not need to know the implementation details of those classes. In fact, to
avoid round-trip problems, the source code of the class generated by the DML compiler
should not be accessible to the programmers: If programmers could modify the code
generated by the DML compiler, the DML compiler would not be able to regenerate the
code when some part of the DML domain specification changes.
In Figure 5.1, I illustrate the expected effect of using the DML in the tool chain typically
used for the development of Java applications. The DML compiler is the only element
that is new. It reads a set of DML source files, which specify the structure of a domain
model, and generates a set of Java source files that implement the structure of that

101

102

Domain Modeling Language

Java Source

DML Source

Standard Java Tools

DML compiler

Java Classes

Domain Classes
Sources

May be used by IDEs

Java Source

Standard Java Tools

Java Classes

Figure 5.1: The effect that the DML has on the tool chain typically used for Java
application development. The dashed line separates the two different scenarios of
tool chain usage. The scenario above the line corresponds to the situation where the
DML is not used. The scenario below the line illustrates the changes caused in the
tool chain by using the DML. Rectangles represent implementation artifacts of the
development process. The rectangles with a white background represent the artifacts
that are edited by the programmers, whereas the rectangles with a gray background
are tool-generated artifacts that the programmers never modify manually. The large
arrows represent the transformation of one kind of artifacts into another kind of
artifacts, performed by the execution of the development tools indicated inside the
arrow. Finally, the single arrow pointing from the Domain Classes Sources to the Java
Source indicates that the former artifacts may be used by IDEs to help programmers
in the development of the latter.

domain model. I decided to generate Java source code, rather than compiled classes, to
eliminate dependencies between this transformation process and other Java classes. As
we shall see, the Java source code generated by the DML compiler may need additional
application-specific classes to compile. Yet, the DML compiler itself does not need any
other application-specific Java class to perform the transformation. So, by generating
only source code, the DML compiler may be executed at any time during the development
process.

5.2

Grammar Notation and Lexical Structure

The syntax of the DML language is defined using a context-free grammar that I introduce
in the following sections. To present this grammar, I use the notation that is used in the
Java Language Specification [Gosling et al., 2005, Chapter 2]:

Nonterminal symbols are shown in italic type, with no spaces between words and
with the first letter of each word capitalized. For example, the following are nonter-

5.2 Grammar Notation and Lexical Structure

minal symbols: Identifier, DomainSpecification, and EntityTypeDeclaration.


Terminal symbols are shown in fixed width font. Examples of terminal symbols
are: class, }, ;, and playsRole.
Production rules are typeset with the left-hand side nonterminalthe nonterminal
symbol defined by the rulefollowed by a colon, on a line by itself. Then, on each
of the following lines, there is a sequence of symbols that defines an alternative
right-hand side of the rule. Thus, the rule

QualifiedName:
Identifier
QualifiedName . Identifier
defines the nonterminal QualifiedName as either an Identifier, or a QualifiedName,
followed by the terminal . and an Identifier. This rule is part of the syntactic
grammar for the DML language and defines the nonterminal QualifiedName as a
sequence of one or more identifiers (explained below) separated by dots.
The suffix opt, which may appear on the right-hand side of a rule, indicates that the
symbol that precedes the suffix is optionalthat is, that in fact the right-hand side
corresponds to two alternative right-hand sides, one where the symbol occurs and
another where the symbol does not occur.
A lexical grammar for the Java programming language is given in Chapter 3 of the
Java Language Specification [Gosling et al., 2005]. This grammar defines how sequences
of Unicode characters are translated into a sequence of input elements, which may be
white space, comments, or tokens. The tokens, which consist of identifiers, keywords,
literals, separators, and operators, form the terminal symbols for the syntactic grammar
of Java.
The DML is a much simpler language. For instance, in DML there are no literals nor
operators. DML uses only identifiers, a few keywords, and operators. Yet, because DML
must integrate seamlessly with Java, I use the lexical grammar defined for Java to define
the lexical structure for the DML language. Thus, the syntax of DML for such things as
comments, identifiers, and white space is the same as in Java.
Note that, even though the DML language does not use most of the keywords of Java
nor any literals, the identifiers used in a DML specification must follow the restriction that
they cannot be a keyword, a boolean literal, or the null literal, because these identifiers
will appear as identifiers in the Java source code generated by the DML compiler.
In the grammar of DML presented in the following sections I use the nonterminal
symbol Identifier as it is defined in the Java lexical grammar: Informally, as a sequence

103

104

Domain Modeling Language

DomainSpecification:
DomainDeclarationsopt
DomainDeclarations:
DomainDeclaration
DomainDeclarations DomainDeclaration
DomainDeclaration:
ValueTypeDeclaration
EntityTypeDeclaration
AssociationDeclaration
Listing 5.1: Syntactic rules for a domain specification. DomainSpecification is the
goal symbol for the DML syntactic grammar.

of Unicode characters that start with a letter, and is followed by any number of letters
or digits. Furthermore, I use the nonterminal symbol DecimalNumeral as it is defined in
the Java lexical grammar, also: Either as the digit 0, or as a sequence of digits starting
with a digit from 1 to 9. Finally, I use the nonterminal QualifiedName as defined by the
production given above. All the remaining nonterminal symbols are defined in one of the
grammar fragments shown in the following sections.

5.3

Domain Specification

The DML language allows us to represent only the structural aspects of a domain model. It
has no constructs to describe the behavior of a program. So, what the DML compiler reads
and processes should not be called a program. Rather, I call it a domain specification.
The syntax of a domain specification is defined by the grammar rules shown in Listing 5.1. The nonterminal symbol DomainSpecification is the goal symbol of the DML
syntactic grammar.
According to these rules, a domain specification is a sequence of zero or more domain
declarations, which, in turn, are either a value type declaration, an entity type declaration,
or an association declaration. The following sections describe, in detail, the syntax and
the semantics of each of these domain declarations.
Each domain declaration introduces a new name that may be used in other parts of the
domain specification to refer to the domain element introduced by this declaration. Thus,
the only restriction to the order of domain declarations is that the names are introduced
before they are used.

5.4 Value Types

ValueTypeDeclaration:
valueType ValueTypeName AliasClauseopt ;
AliasClause:
as ValueTypeName
ValueTypeName:
QualifiedName
Listing 5.2: Grammar rules for the syntax of a value type declaration.

5.4

Value Types

In the DML language, I distinguish between value objects and entities, as described
in Section 2.2.3. Value objects, unlike entities, are immutable, are not persistent by
themselves (only as part of entities), and do not participate in bidirectional associations.
Therefore, the code used to implement a value type is significantly different from the code
used to implement an entity type.
Furthermore, often the value types used in a domain model are not specific of that
domain model. Instead, they may be used across many different domains. So, in many
cases value types are provided already by a third-party framework or toolkit. If, however, a
value type does not exist yet, the implementation of its structure is mostly trivial, because
value objects are immutable.
For these reasons, the DML language does not allow the specification of new value
types. Yet, value types are needed to define new entity types: All the attributes of an
entity type must be of some value type. That is why the DML language has value type
declarations.
New value types are introduced in a domain specification by value type declarations,
which follow the syntax specified by the grammar productions shown in Listing 5.2.
Each value type declaration introduces into the domain specification the name of a
value type. The definition of this value type, however, is made outside the DMLs domain
specification.
In its simplest form, a value type declaration is only the keyword valueType followed
by the fully-qualified name of a new value type. That name becomes a new valid name
that may be used wherever a value type is expected. When the alias clause is used, the
name that is introduced into the domain specification as a new value type is the name
that follows the keyword as; the real name of the value type is used only by the DML
compiler to generate the code with the appropriate types.

105

106

Domain Modeling Language

valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType
valueType

boolean;
byte;
char;
short;
int;
float;
long;
double;
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Short
java.lang.Integer
java.lang.Float
java.lang.Long
java.lang.Double
java.lang.Number
java.lang.String

as
as
as
as
as
as
as
as
as
as

Boolean;
Byte;
Character;
Short;
Integer;
Float;
Long;
Double;
Number;
String;

Listing 5.3: Default value types in the DML language. The first eight value type declarations introduce the names of the eight primitive types in Java as value types. The
following eight declarations introduce the wrapper reference types, which, because of
the alias clause, should be used without the java.lang package qualifier. Likewise
for the last declaration, which introduces the name String.

As an example, I show in Listing 5.3 the value type declarations for the value types
defined by default in the DML language.

5.5

Entity Types

Entity types are the basic elements of a domain specification. Each entity type describes
the structure of a set of similar entities, which are the objects that represent the domains
state.

Entities hold their state in a set of attributes, which may change during the

execution of an application as the state of an entity changes. The value of each attribute,
however, must be a value objectthat is, the type of each attribute must be a value type.
Entities may refer to other entities only through the traversal of the associations between
their types.

5.5.1

Syntax of Entity Type Declarations

A new entity type is introduced in a domain specification by an entity type declaration,


which follows the syntax specified by the grammar rules shown in Listing 5.4 on the next
page.

5.5 Entity Types

EntityTypeDeclaration:
class EntityTypeName Superopt EntityTypeBody
EntityTypeName:
QualifiedName
Super:

extends EntityTypeName
EntityTypeBody:

;
{ AttributeDeclarationsopt }
AttributeDeclarations:
AttributeDeclaration
AttributeDeclarations AttributeDeclaration
AttributeDeclaration:
ValueTypeName Identifier ;
Listing 5.4: Grammar rules for the syntax of an entity type declaration.

An entity type declaration is a stripped-down version of a class declaration in the


Java programming language. In fact, many entity type declarations in DML are valid
class declarations in Java, even though only a few class declarations in Java are valid
entity type declarations in DML.
As in Java, after the keyword class comes the name of the new entity type. But,
whereas in Java the name of the new class must be an Identifier, in DML the EntityTypeName may be a qualified name.
When the new entity type is a subtype of another entity type, we represent that inheritance relationship by using the keyword extends followed by the name of the entity
type of which the new type is a subtype. Note that the name after the keyword extends
must be the name of an entity type introduced by a previous entity type declaration. In
DML, an entity type cannot be a subtype of a value type.
The body of an entity type declaration is used in DML only to specify the entity types
attributes, using a syntax similar to the syntax of field declarations in Java. If an entity
type does not have new attributes (other than those inherited), the body of its declaration
may be replaced by a semicolon. An example of such a simple entity type declaration is

class Bank;
Yet, in general, entity types have one or more attributes. So, typical examples of entity
type declarations are shown in Listing 5.5 on the following page.

107

108

Domain Modeling Language

class Client {
String name;
}
valueType a.business.api.Money as Money;
class Account {
Money balance;
}
class ClientAccount extends Account {
boolean closed;
}
Listing 5.5: Examples of entity type declarations in DML. Both Client and
Account are top-level entity types that do not inherit from any other type. The
ClientAccount type, however, is a subtype of Account. Also, the Account entity
type uses a value type introduced in the previous declaration.

5.5.2

Semantics of Entity Type Declarations

I specify the semantics of a domain specification by prescribing the minimal set of Java
classes that a compiler of the DML language must produce when it compiles the domain
specification. Also, I prescribe which fields and methods each generated class must have.
Different compilers for the DML language may vary in the classes they produce. Yet, all the
compilers must conform to the minimal interface specified here, because programmers
depend on this interface to develop the rest of the domain model.
Therefore, in this section, I specify the semantics of an entity type declaration by
describing the minimal Java interface that a DML compiler must produce when it compiles
an entity type declaration.
We have seen in Section 3.3.1 that classes from a UML class diagram are implemented
naturally as classes in Java: Typically, each class of a class diagramcorresponding to
an entity type in DMLis implemented by a single class in Java.
In DML, however, an entity type is implemented by two classes, as depicted in Figure 5.2 on the next page. Each class implements one of the two aspects of a domain
entity:

The first classthe state classis abstract and implements the domain entitys
structural aspects. The DML compiler generates this class from the domain specification. Thus, programmers should not edit this class manually.
The second classthe behavior classextends the state class and implements the
domain entitys behavioral aspects. This class, unlike the state class, cannot be

5.5 Entity Types

109

AState
class A {
ValueType1 attr1;
...
ValueTypeN attrN;
}

DML Compiler

getAttr1(): ValueType1
setAttr1(ValueType1 val)
...
getAttrN(): ValueTypeN
setAttrN(ValueTypeN val)

Figure 5.2: The result of compiling an entity type in DML. An entity type A is transformed into two Java classes AState and A. The abstract class AState has a getter
and a setter for each of the entity types attributes. The gray background in the
class AState indicates that the DML compiler generates this class and, thus, that
programmers should not edit the class.

generated by the DML compiler, because the domain specification does not have
any behavior specification.1 Instead, the implementation of this class is the responsibility of the domain model programmers: This is the class where programmers
implement the behavior of the entity.

The DML compiler uses this compilation strategy of separating a class in two to avoid
round-trip problems: Whenever we change the domain specification, we must reexecute
the DML compiler to update the classes generated in previous compilations, but, because
programmers do not edit the state classes, the DML compiler may regenerate those classes
from scratch each time it runs.
The state class that the DML compiler generates from an entity type has both a getter
method and a setter method for each of the entity types attributes. The DML compiler
forms the names of these methods by concatenating the prefixes get and set, respectively, with the name of the attribute, after capitalizing the first letter of the attribute. So,
given an entity object obj, the method call obj.getAttrName() returns the objs value
for the attribute named attrName, whereas the call obj.setAttrName(newVal) sets
the value of the attribute to the value newVal. No other methods can access the attribute.
Even though a state class has all the attributes of an entity type and no abstract
method, it must be abstract, because it does not represent an entity. An entity contains both state and behavior, but the instances of a state class contain only the state.
Therefore, the state class is abstract to prevent the creation of instances of an incomplete
type.
1

In fact, the DML compiler generates an empty behavior class if the class does not exist yet, so that we may
compile the domain model without errors. If the class exists, however, the DML compiler does not modify it.

110

Domain Modeling Language

AState
...

class A {
...
}

A
DML Compiler

class B extends A {
...
}

BState
...

Figure 5.3: The result of compiling an hierarchy of entity types in DML. The Java
state class that results from the compilation of an entity type extends the behavior
class that corresponds to its supertype: In this case, the class BState extends A.

The class that represents both the state and the behavior of an entity is the behavior
class, which implements the behavior and inherits the state from the state class. Note
that the methods in the behavior class may access the state of an instance by using the
getter and the setter methods inherited from the state class.
As a matter of fact, the state class is meant to be used only as the superclass of
its corresponding behavior class. No other classes should extend it. Also, no fields or
methods or any other part of the code should refer to a state class as a type. Instead,
all the remaining code should use the types that correspond to behavior classes. For
instance, in Figure 5.3, I show that, if B is a subtype of A, the class that implements the
structure of B, the class BState, must extend the class A, rather than the class AState.
If the class BState extended the class AState instead of A, it would not inherit the
behavior of A, as expected.
Finally, there is only one restriction regarding the implementation of a behavior class
that it should not have any state of the entity type; the state belongs in the state class.
Besides that restriction, programmers are free to implement the behavior class in whatever
way they want. For instance, programmers may make the class abstract, if it is not meant
to have any instances; they may make the class implement any number of interfaces; or
they may add the methods they need to implement the entitys behavior. In particular,
they may override any of the methods that are specified in this section, which the behavior
class inherits from the state class.

5.6 Associations

5.6

Associations

In UML, relations are called associations and they may connect two or more classes to
represent a relationship that exists among the objects that the connected classes describe.
The DML language uses the name association, also, to represent relationships. But,
unlike UML, DML does not allow the specification of associations with an arbitrary arity;
in DML all associations must be binary. I chose to support only binary associations in
the DML language mostly for pragmatical reasons.
First, because even though the semantics of a generic n-ary relation is naturally
defined in mathematical terms as a set of tuples, the semantics of n-ary relationships as
a domain modeling construct is either ill-defined, or confusing and error-prone [Gnova,
Llorens, and Martnez, 2002].
Second, because the implementation of n-ary associations in the object-oriented programming paradigm is not that simple. In an object-oriented program, programmers use
binary associations to navigate in the object graph, going from one object to another by
traversing a linkthat is, an instance of an associationbetween the two objects. Typically, programmers implement such binary links in an object-oriented domain model as
references or pointers from one object to the other. When the association is not binary,
however, to find the object at the other end of a link, we need more than an object:
We need all the objects in the link but one. Thus, we no longer can use simple references between objects to implement such associations. But this is more of a concern
for the implementation of the DML compiler, which would need to generate the code to
implement the correct semantics (provided that we could agree on one). If we ignore the
implementation problems raised by non-binary associations, however, there are still usage problems: Traversing a binary link in a typical implementation of an object-oriented
domain model may be linguistically quite different from traversing links that relate more
than two objects.
The third pragmatical reason for limiting the DML to binary associations, was the
observation that associations among three or more entity types are seldom used in the
design or in the implementation of a domain model: Most probably because of the two
reasons above, programmers shy away from n-ary associations when they develop their
domain models.
The fourth and final reason is that limiting the DML to binary associations does not
prevent the implementation of a domain model. In the rare cases where programmers use
a non-binary association, it is possible to replace that association (often with benefits to
the design of the domain model) with an entity type and several binary associations: The
new entity type represents, as first-class entities in the domain model, the links of the
non-binary association that it replaces.

111

112

Domain Modeling Language

Therefore, following the fourth guiding-principle of Section 1.2.2, I considered that the
eventual benefits of supporting n-ary associations in DML were not worthy of the effort
necessary to implement them.

5.6.1

Syntax of Association Declarations

An association declaration, which adds an association to a domain specification, is a toplevel construct in the DML languagethat is, it is a construct that stands by itself, rather
than being part of, or having to appear subordinated to another construct.
This syntax for associations contrasts with how other approaches to the problem of
making associations explicit in the programming language propose to represent associations. Many of such approaches propose to represent associations with new constructs
that programmers should use in each of the associated classes. This solution, however,
splits the information about the association between the two classes, making both the
understanding and the maintenance of a domain model more difficult. Thus, I argue that
an association declaration should be a single construct, as it is in the DML language.
In Listing 5.6 on the next page, I show the grammar rules that specify the syntax of an
association declaration. This set of rules concludes the syntactic grammar of the DML
language.
The keyword association introduces an association declaration and is followed by
the name of the association, an identifier. As we shall see in the following section, the DML
compiler uses this identifier to name a static field in each of the classes that participate
in the association. Therefore, this name must be unique throughout the inheritance
hierarchy of each of those classes. To avoid name clashes, programmers should adopt a
naming convention that distinguishes the names of associations from the names of other
members of a class (e.g., inner classes).
In a relationship among several entities, each of the entities plays a role in the relationship. Thus, the DML language uses role declarations to identify the entity types that
participate in an association. As in DML all the associations are binary, an association
declaration has exactly two role declarations.
Each role declaration specifies the type of the entities that play the declared role in
the association, the name of the role, and the multiplicity of the role. Yet, both the name
and the multiplicity of the role are optional. If the name of a role is not specified, then
the association is not traversable in that directionthat is, it is not possible to reach
the entities that play that role from the entities in the other end of the association. I
shall discuss this further in the following section, where I present the semantics of an
association declaration.
If the multiplicity of a role is not specified, however, a default multiplicity of 0..1

5.6 Associations

AssociationDeclaration:
association Identifier RoleDeclarations
RoleDeclarations:
{ RoleDeclaration RoleDeclaration }
RoleDeclaration:
EntityTypeName playsRole Identifieropt RoleBody
RoleBody:

;
{ MultiplicityOption }
MultiplicityOption:

multiplicity MultiplicityValues ;
MultiplicityValues:
MultiplicityRange
MultiplicityValues , MultiplicityRange
MultiplicityRange:
MultiplicityUpperBound
MultiplicityLowerBound .. MultiplicityUpperBound
MultiplicityUpperBound:

*
DecimalNumeral
MultiplicityLowerBound:
DecimalNumeral
Listing 5.6: Grammar rules for the syntax of an association declaration. Each
association declaration has exactly two role declarations, identifying the two entity
types that participate in the relationship. The nonterminal symbol DecimalNumeral
is defined in the Java lexical grammar [Gosling et al., 2005] either as the digit 0 or as
a sequence of digits that start with a non-zero digit.

113

114

Domain Modeling Language

association AccountOwnership {
Client playsRole owner {
multiplicity 1; // it is equivalent to 1..1
}
ClientAccount playsRole account {
multiplicity 1..*;
}
}
association AccountGroup {
CheckingAccount playsRole checking {
multiplicity 1;
}
SavingsAccount playsRole savings {
multiplicity *; // it is equivalent to 0..*
}
}
Listing 5.7: Examples of association declarations in DML. Both associations are bidirectional one-to-many associations. Note that the role names and the multiplicities
match those specified in the class diagram shown in Figure 2.4 on page 24.

is assumed. Moreover, the multiplicity range * is a shorthand for 0..*. Finally, a


multiplicity range of the form N, for any positive integer N , is a shorthand for N..N.
Thus, every role has a multiplicity value, either implicitly or explicitly declared, which is
a sequence of one or more ranges with a lower bound and an upper bound.
Note that the syntax for an association declaration could be made simpler. For instance, we could eliminate the keywords playsRole and multiplicity, as well as
some of the curly braces. The reason for having this syntax, however, is that I designed
it with extensibility in mind. For example, I foresee the need to add other options to a
role declaration. Yet, given the current syntax for the multiplicity option, the syntactic
changes to add other options would be minimal.
To conclude this section I show, in Listing 5.7, two association declarations that
represent two of the associations from the banking domain.

5.6.2

Semantics of Association Declarations

Like before, when I specified the semantics of an entity type declaration, I specify the
semantics of an association declaration by prescribing the minimal Java interface that a
DML compiler must generate for each association declaration.
In this case, however, the DML compiler does not need to generate any new classes to

5.6 Associations

implement an association declaration. Rather, it must generate new methods for each of
the entity types that play a role in the association.
The semantics that I specify here tries to respect the pragmatics of object-oriented
programming. Object-oriented programmers do not, as a common practice, implement
a binary association or the associations instancesthe linksas first-class objects in
the program. Instead, the object-oriented common practice is to implement a binary
association between two classes A and B, by adding to each of the classes A and B a
reference to the elements of the class in the other side of the association: In the class
A we add a reference to elements of B; in the class B we add a reference to elements of
A. These references, however, are not typically accessible by the classes clients. As we
have seen in Section 3.3.2, the usual approach is to provide methods that access these
references.
Thus, because programmers should use the public methods, rather than the private
references, the semantics that I specify here prescribes only which methods must a DML
compiler generate. It does not specify how the compiler should implement those methods.
It does not specify either, how the generated code should implement the associations
links, whether with references in each of the classes, or with any other solution.
To simplify the presentation below, given an association declaration with two role
declarations, I use the term opposite type of a role declaration to refer to the entity type
of the other role declaration. For example, given the following association declaration

association Rel {
A playsRole role1;
B playsRole role2;
}

I say that the opposite type of the first role declaration is B, and that the opposite type of
the second role declaration is A.
A compiler for the DML language must generate at most two sets of related methods for
an association declarationone set of methods for each of the role declarations that have
a role name specified. If a role declaration has no name, then the DML compiler should
not generate any methods for that role declaration. The methods generated from a role
declaration belong to the opposite type of that role declaration. They allow the traversal
from an instance of the opposite type to one or more instances of the role declarations
type.
Therefore, an association where both role declarations have a name is a bidirectional
association, whereas if only one role declaration has a name, the association is unidirectional. An association declaration where none of the role declaration have a name is
meaningless for a domain specification, because no code will be generated from it.

115

116

Domain Modeling Language

The signature of the methods that a DML compiler must generate for a role declaration
depends only on the properties of that role declaration:

The roles multiplicity determines the set of methods to generate. Although the
multiplicity option may have many different values, the DML compiler separates the
roles into two disjoint classes: (1) the roles that have a multiplicity upper-bound of
one; and (2) the roles that have a multiplicity upper-bound greater than one.2 The
multiplicity upper-bound of a role declaration is the maximum of the upper-bounds
of all the multiplicity ranges (there is at least one) in the role declaration. The upperbound of a multiplicity range of the form M..N is infinite, if N is the terminal symbol

*, and the value of the integer N, otherwise. I shall present below, separately, the
set of methods that must be generated for each of these cases.
The roles name determines the exact name of each method. The name of the role
with the first letter capitalized appears in all the methods names, either with a
prefix only, or with both a prefix and a suffix. Below, when I present the methods to
generate, I show in italic the part of the name of each method that must be replaced
by the roles name.
The roles type determines the type of the argument, or the return type of some of
the methods to generate.

Regardless of the multiplicities involved, the methods that implement an association


must allow us to do each one of the following tasks: (1) create a new link between two
objects, (2) remove an existing link between two objects, and (3) traverse from one object
in one end of a link to the object on the other end. The specific signature of the methods
that allow us to do this, however, depends on the multiplicity of each role. I shall present
each case separately.

5.6.2.1

Roles with a multiplicity upper-bound of one

If the multiplicity of a role declaration has an upper-bound of one, then an object of the
opposite type may refer to at most one object of the roles type. This is the simplest case
of an association, which is typically implemented with a getter and a setter method.
In Figure 5.4 on the next page, I show the signature of the methods that a DML
compiler must generate for a role declaration with a multiplicity upper-bound of one. Only
the first two methods, shown in boldface, are necessary to implement the association. The
other two methods may be trivially implemented by using the mandatory methods. Yet,
they make the class more convenient to use, and, therefore, more programmer-friendly.
2

Roles with a multiplicity upper-bound lesser than one do not make sense.

5.6 Associations

association RelAB {
A playsRole role {
multiplicity 0..1;
}
B playsRole;
}

117

BState
DML Compiler

...
setRole(A role)
getRole():A
hasRole():boolean
removeRole()

Figure 5.4: Methods that a DML compiler must generate for a role declaration with
a multiplicity upper-bound of one. The part of a methods name that depends on the
name of the role is shown in italic. The methods in boldface are the core methods.
The other methods are optional methods.

The method setRole is the setter method. It allows us to create or to eliminate


a link between an instance of class B and an instance of class A. To create a link, we
should call the method with an instance of class A as argument. In this case, the method
execution creates a new link between the receiver and the argument of the method call.
But, because we can have at most an instance A linked to each instance of B, if there
was already a link between the receiver of the method call and another instance of class

A, the method must eliminate that link before it creates the new link. Finally, the call to
the setter method with a null argument eliminates any current link that may exist for
the receiver of the method call.
The method getRole is the getter method. Given an instance of the class B, b, the
method call b.getRole() returns the instance of the class A that is related to b, if such
an instance exists, or null, otherwise. This method is the method that allows us to
traverse the association.
The method hasRole returns true if there is a link between the receiver of the method
call and an instance of class A. Otherwise, it returns false. Calling this method on an
object obj is equivalent to evaluate the Java expression obj.getRole() != null.
The method, however, may be implemented more efficiently by the DML compiler.
Finally, the method removeRole removes an existing link that may exist for the
receiver of the method call. It is equivalent to call the setter method with a null argument.

5.6.2.2

Roles with a multiplicity upper-bound greater than one

The difference between this case and the previous is that the object of the opposite type
may have multiple links, at the same time, with objects of the roles type. So, when we
create a new link, we do not remove any existing link, as in the previous case. The existing
links must be removed explicitly, by specifying which object is on the other end of the
link that should be removed. Furthermore, when we traverse the association, we may
reach multiple objects. Thus, the getter method, in this case, must return a collection of

118

Domain Modeling Language

AState

association RelAB {
A playsRole;
B playsRole role {
multiplicity 0..*;
}

DML Compiler

...
addRole(B role)
removeRole(B role)
getRoleSet():Set<B>
getRoleCount():int
hasAnyRole():boolean
hasRole(B b):boolean

Figure 5.5: Methods that a DML compiler must generate for a role declaration with
a multiplicity upper-bound greater than one. The part of a methods name that
depends on the name of the role is shown in italic. The methods in boldface are the
core methods. The other methods are optional methods.

objects.
In Figure 5.5, I show the signature of the methods that a DML compiler must generate
for this case. Like in the previous case, there is a set of core methods, and a set of
convenience methods.
The method addRole creates a new link between the receiver of the method and an
instance of the class B passed as argument to the method. If the argument of the method
is null, the method does nothing.
The method removeRole eliminates the link between the receiver of the method and
the instance of the class B passed as argument to the method. If no such link exists, or
if the argument is null, the method has no effect.
The method getRoleSet returns the set of instances of the class B that have a
link with the receiver of the method. The value returned by this method is always an
instance of a class that implements the java.util.Set interface, but it must satisfy
some conditions that are specified below.
Finally, the remaining three methods may be defined by the expressions to which they
are equivalent:

// the expression
obj.getRoleCount()
// is equivalent to the expression
obj.getRoleSet().size()
// the expression
obj.hasAnyRole()
// is equivalent to the expression
(! obj.getRoleSet().isEmpty())
// the expression

5.6 Associations

obj.hasRole(b)
// is equivalent to the expression
obj.getRoleSet().contains(b)

5.6.2.3

Bidirectional associations

When a new link is created for a bidirectional association, that new link must become
traversable in both directions, rather than in only one, regardless of how that link is
created. Likewise for the removal of a link.
A link is an instance of an association between two objectsthat is, it represents
that the two objects are related by the relationship that the association represents. A
link, however, is typically implemented as two separate references: Each of the objects
in the link keeps a reference to the other. Yet, when a link is created or eliminated both
references must be updated to reflect the change.
Thus, in a bidirectional association, we should be able to create the same link by
calling either one method for the object in one end of the link, or calling an equivalent
method for the object in the other end. For instance, if we merge the two declarations for
the association RelAB (see Figure 5.4 on page 117 and Figure 5.5 on the facing page), the
execution of the code a.addRole(b) should be equivalent to the execution of the code

b.setRole(a).
As a final example, consider that we have the following association declaration

association Rel {
A playsRole a { multiplicity 0..1; }
B playsRole b { multiplicity 0..1; }
}

Moreover, consider that we have two instances of the class A, a1 and a2, and two instances of the class B, b1 and b2. Between a1 and b1 there is a link, and another
link exists between a2 and b2. Thus, a1.getB() returns b1, b1.getA() returns a1,

a2.getB() returns b2, and b2.getA() returns a2.


Consider now that we execute either a1.setB(b2), or the equivalent b2.setA(a1).
Either of the calls creates a new link between a1 and b2, but, as the multiplicity upperbound of both roles is one, the previous links must be eliminated. Thus, after the execution of either of the calls, we have that a1.getB() returns b2, b1.getA() returns

null, a2.getB() returns null, and b2.getA() returns a1.

119

120

Domain Modeling Language

5.6.2.4

Sets returned by the method getRoleSet

The specification for the method getRoleSet, presented above, does not specify the exact nature of the value returned by the methodfor example, whether the value returned
is an immutable set or a mutable set. Yet, it is important to specify these details, so that
programmers know what they can and cannot do with the result returned.
In the DML language, the value returned by a call to the method getRoleSet is a
mutable set that is backed up by the association and the receiver of the method call.
This means not only that programmers may add and remove elements from the set, but
also that those changes correspond, in fact, to the creation and elimination of association
links. Furthermore, if a new link is added to or removed from the object that backs up
the set, the set reflects that change.
The advantage of having this specification is that we may use all the methods available
in the java.util.Set interface to manipulate the links of an object. For instance, we
may remove all the links of an object, o, with the code o.getRoleSet().clear(). Or,
probably more useful, we may iterate over the set and remove the elements that satisfy
some condition.
Finally, a consequence of this specification is that the method call o1.addRole(o2)
is equivalent to o1.getRoleSet().add(o2). Likewise for the method that removes a
link. Thus, the method getRoleSet is sufficient to implement a role declaration with a
multiplicity upper-bound greater than one.

5.6.2.5

Association objects and their listeners

Having different ways to create and to remove a link makes the domain model more
programmer-friendly, because programmers may choose what is more convenient for
them in each situation. Yet, all these equivalent approaches make the domain model
more complex, and, eventually, more confusing, also. In particular, having various entry
points for the same functionality raises the question of which method should we override
if we want to customize that functionality.
Imagine that we want to execute some code whenever a new link for the association

RelAB is created between an instance of the class A and an instance of the class B.
Should we override the method setRole in the class B? That would work for the links
created by that method. But a link may be created by calling the method addRole in
the class A, also, and we do not know whether that method calls the method setRole
or not. Yet, if we override the method addRole instead, we have similar problems. Even
if we override both methods, we are not sure to catch all the link creations because links
may be created when we add objects to a set returned by the method getRoleSet.

5.6 Associations

interface Association<C1,C2> {
void add(C1 o1, C2 o2);
void remove(C1 o1, C2 o2);
Association<C2,C1> getInverse();
void addListener(AssocListener<C1,C2> listener);
void removeListener(AssocListener<C1,C2> listener);
}
interface AssocListener<C1,C2> {
void beforeAdd(Association<C1,C2> assoc, C1 o1, C2 o2);
void afterAdd(Association<C1,C2> assoc, C1 o1, C2 o2);
void beforeRemove(Association<C1,C2> assoc, C1 o1, C2 o2);
void afterRemove(Association<C1,C2> assoc, C1 o1, C2 o2);
}
Listing 5.8: The Java generic interfaces Association and AssocListener. The
DML compiler uses objects of the Association type to implement an association in
Java. Programmers may customize the association operations by adding listeners to
an association object.

To solve this problem, I propose to add yet another way to create and to remove links
from an association: A common point in the code that must be executed whenever a link
is created or removed, regardless of how that is done.
The key idea is to have an object that represents an association. The basic operations
of that object are a method to add a new pair of objects to the association, and a method
to remove a pair of objects from the association. The first method creates a new link, the
second removes an existing link. These methods must be called to create or to remove a
link. In fact, all the methods described previously that create or remove links may call
the add or the remove operations of this new object. The semantics must be the same.
The association object gives us a central point of execution for the operations that
change an association. Now, we need some mechanism to specialize those operations.
Because each association is represented by an object, rather than by a class, we cannot
use standard class inheritance and method overriding to do that specialization. Instead,
we may use listeners to do it. In Listing 5.8, I show both the interface Association
and the interface AssocListener that will allow us to specialize the creation and the
removal of a link.
The method addListener adds a new listener to an association object, whereas
the method removeListener removes a listener. Even though these methods may be
used at runtime to change the set of listeners for an association object dynamically, the
common usage is to add one or more listeners to an association object at class-load-time
and use that set of listeners throughout the entire program.
When an association object has some listeners registered, the methods add and

121

122

Domain Modeling Language

association Rel {
A playsRole a {
multiplicity 0..1;
}

abstract class AState {


static Association<A,B> Rel;
...
}
DML Compiler

B playsRole b {
multiplicity 0..*;
}
}

abstract class BState {


static Association<B,A> Rel;
...
}

Figure 5.6:

Static fields that a DML compiler must generate to hold the


Association objects. The name of the static field in each class is the name of
the association, as given in the association declaration. The association object in
each class is the inverse of the association object in the other class.

remove call the appropriate methods of the registered listeners, following the same order by which the listeners were added to the association object. The method add first
calls the method beforeAdd of each listener, then creates the link, and, finally, calls
the method afterAdd of each listener. Mutatis mutandis for the method remove. Note
that a listener may cancel the creation (or the removal) of a link, if its beforeAdd (or

beforeRemove) method throws an exception.


Using custom association listeners, it is easy to specialize an association. But, before
we can do that, we need to know how to access the objects that represent associations
in a domain model. My proposal is to make them accessible through static fields in the
classes that participate in the association, as depicted in Figure 5.6.
With this final piece in place, we may now specialize the creation of a link, as intended.
In Listing 5.9 on the next page, I show a sketch of the code that we may add to the behavior
class A to accomplish that.

5.7

Implementation of a Domain Specification

The specification of the DML language prescribes in detail the interface of the classes
that a conforming DML compiler must generate; it does not, however, dictate how that
interface should be implemented, even though it gives, now and then, some hints about
possible implementation strategies.
Therefore, different compilers for the DML language may generate different source code
to implement a domain specification: Either because they must generate domain models
with different properties, or simply because they use different implementation strategies.
In fact, the simplicity of the DML language is a conscious design decision to promote
experimentation with different implementation strategies, which may affect significantly

5.7 Implementation of a Domain Specification

class A extends AState {


static class RelListener implements AssocListener<A,B> {
void beforeAdd(A a, B b) {
// do something
}
// ... implementation of remaining methods
}
static {
// specialize the Rel association
Rel.addListener(new RelListener());
}
}
Listing 5.9: Specialization of an association using an AssocListener. An inner
class is used to specify the listener. Then, a static initializer registers a listener with
the association object that is stored in the static field Rel of the superclass AState.

the performance and the memory footprint of an application. Given the reduced number
of constructs in the language, creating a new code generator backend for a DML compiler
should be well within the reaches of any software development team.
In this section, I give an outline of one of the code-generator backends that I implemented for the DML. I will not describe how I implemented the compiler or the codegenerator.

Rather, I describe which code is generated by the backend for a domain

specification.
The backend that I describe here corresponds roughly to the one used in the FnixEDU
project [FenixEDU], which we shall see in Chapter 7. The code that this backend generates tries to accomplish a good equilibrium between two different requirements for the
generated code: efficiency, and readability. Efficiency, because it is used in a large project
with many entities, where efficiency was a concern. Readability, because programmers
may need to read the code generated by the DML compiler when they are debugging the
application.
Even though many other strategies exist for implementing a domain specification, I
believe that the implementation that I outline here provides a good starting base for many
projects.

5.7.1

Using the JVSTM to Make a Transactional Domain

Throughout this dissertation, I propose to implement domain models that exhibit transactional properties. So, it comes out naturally that the implementation that I describe here
generates a transactional domain, by leveraging on the JVSTMthe Java implementation

123

124

Domain Modeling Language

of the STM model that I propose in Section 4.3.


By using the JVSTM to implement the domain specification, we obtain a domain model
that is transactional, a domain model that has all its state stored in transactional memory.
We are, therefore, able to use atomic actions to implement some of the operations on the
domain model. Those operations may be methods implemented in the behavior classes,
or more coarse-grained operations that are defined in the layers above the domain model.
In fact, because the JVSTM supports nested transactions, we may have atomic actions
at both levels: We may use atomic actions to implement some of the behavior methods,
which may then be called inside more coarse-grained atomic actions.
Note that, to implement the behavior classes of a domain model, programmers need
to know which methods exist in the state classes from which the behavior classes inherit.
Also, they need to know what those methods do. But they should not need to know the
implementation details of the state classes. Yet, programmers need to know whether those
classes are transactional or not, if we want that they take advantage of the transactional
properties of the domain model.
I argue that, to build rich domain models, it is indispensable that the domain model
be transactional. So, the intended semantics for a DML domain specification is that
it specifies a transactional domain model, even though the language may have more
relaxed implementations in some cases. It turns out, however, that the implementation
of a domain specification as a transactional domain model is quite simple, if we are using
the JVSTM to do it.

5.7.2

Implementing Entity Types

The DML specification already prescribes that an entity type must compile to a pair of
classesthe state class and the behavior classwith a getter and a setter for each of the
entity types attributes.
What is missing, then, is how to implement those methods. The usual approach used
to implement the attributes of an entity is to use one field for each attribute in the class
implementing that entity type. Each field holds the value of an attribute. To make the
class transactional, however, we need to wrap each value with a VBox, so that a change
in an attribute may become part of an atomic action.
For instance, in Figure 5.7 on the facing page, I show the implementation of the first
entity type declaration from the example given in Listing 5.5 on page 108.
The methods generated for an entity type declaration do not need the annotation

Atomic. They only read or write the box that corresponds to an attribute, so they are
atomic already.

5.7 Implementation of a Domain Specification

public class ClientState {


private VBox<String> name;

class Client {
String name;
}

String getName() {
return this.name.get();
}

DML Compiler

void setName(String name) {


this.name.put(name);
}
}
Figure 5.7: JVSTM-based implementation of the class ClientState. This class is
the state class that results from the compilation of the first entity type declaration
shown in Listing 5.5 on page 108, the declaration of the entity type Client.

5.7.3

Implementing Associations

The straightforward implementation of an entity type is a consequence of the natural mapping between entity types and Java classes. Unfortunately, this ease of implementation
does not extend to the implementation of associations. Therefore, the implementation of
associations requires more work.
To implement an association, we must generate a set of methods for each of the associations roles. We need, also, to create an instance of a class that implements the interface

Association (which is shown in Listing 5.8) for each of the classes participating in the
association; as prescribed by the DML specification, these Association instances are
stored in static fields, and, so, there is not much variability here.
Where we may have different implementation strategies is on the implementation of the

Association interface and on the implementation of the roles methods, provided that
the implementation chosen satisfies the requirements of the DML specification. Namely,
that, regardless of which methods are used to create or remove links from an association,
they invoke the appropriate methods in the association listeners that were previously
added to the associations instance. In fact, the DML specification prescribes a set of
redundant ways for creating and removing links, but requires that all those redundant
ways of doing things be semantically equivalent.
The simplest way of ensuring this equivalence is to implement the creation (or removal)
of a link in a single kernel method, and make all the remaining equivalent methods call
that kernel method. The kernel method is responsible for creating (or removing) a link,
ensuring that both sides of the link are updated consistently. In my implementation, the
kernel methods are the methods add and remove from the interface Association.

125

126

Domain Modeling Language

association AccountOwnership {
Client playsRole owner {
multiplicity 1;
}
ClientAccount playsRole account {
multiplicity 1..*;
}
}
DML Compiler

public class ClientState {


private AssocSet<Client,ClientAccount> accountSet;
...
}
public class ClientAccountState {
private VBox<Client> owner;
...
}
Figure 5.8: Fields used to store the links of an association. Each role generates a
new field in the class of the opposite type to store the objects that are related with the
object at that end of the link.

5.7.3.1

Storing the Associations Links

When we create a link between two objects, we need to store the information that they
are linked somewhere. Two common choices are: (1) making the link a first-class object
and keeping a global set of links for each association, and (2) storing with each object the
object or set of objects with which it relates. The first solution may be preferable in terms
of memory consumption when relations are sparse, but incurs in a performance penalty
when we need to know which objects are related to another object. The second solution is
faster in this latter case, but may require more memory and makes other types of relation
accounting more difficult to implement.
Given the interface prescribed by the DML specification, I opted for the second solution. Thus, each role declaration will generate a new field in the roles opposite class.
The purpose of that field is to hold either a single object or a set of objects, depending
on whether the roles multiplicity upper-bound is one or greater than one, respectively.
Additionally, to ensure the proper transactional semantics, the field must be wrapped
with a VBox, if for a single object, or use a transactional set, otherwise.
In Figure 5.8, I show an example where both types of fields are generated when an
association declaration is compiled. The class AssocSet is a transactional set that
ensures the semantics required by the DML specification for the sets returned for a role

5.7 Implementation of a Domain Specification

association AccountOwnership {
Client playsRole owner { multiplicity 1; }
ClientAccount playsRole account { ... }
}
DML Compiler

public class ClientAccountState {


static Association<ClientAccount,Client> AccountOwnership = ...;
private VBox<Client> owner;
Client getOwner() { return this.owner.get(); }
void setOwner(Client owner) {
AccountOwnership.add((ClientAccount)this, owner);
}
}

Figure 5.9: Implementation of the methods for a role with a multiplicity upper-bound
of one. The setter simply calls the kernel method add from the Associations
instance.

with a multiplicity upper-bound greater than one. We shall see this class in more detail
in Section 5.7.3.3.

5.7.3.2

Implementing the Role Methods

The set of methods to generate for each role depends on the multiplicity of the role. There
are two cases: roles with a multiplicity upper-bound of one, and roles with a multiplicity
upper-bound greater than one. In either case, however, there is a set of core methods
and a set of optional methods. Here, I describe the implementation of the core methods
only; the implementation of the optional methods results trivially from the core methods.
In the first caseroles with a multiplicity upper-bound of onewe have only two
methods: a getter and setter. To implement the getter, we just have to return the value
of the VBox that is used to store the value. The setter, however, creates or removes a
link and must, therefore, call the appropriate kernel method to accomplish that. I show
in Figure 5.9 an example of the code generated in this case.
In the second caseroles with a multiplicity upper-bound greater than onethe implementation is similar, except that now we have one more method to remove a link,
whereas in the previous case the setter took care of that too. I show in Figure 5.10 an
example of the code generated in this case.

127

128

Domain Modeling Language

association AccountOwnership {
Client playsRole owner { ... }
ClientAccount playsRole account { multiplicity 1..*; }
}
DML Compiler

public class ClientState {


static Association<Client,ClientAccount> AccountOwnership = ...;
private AssocSet<Client,ClientAccount> accountSet;
Set<Account> getAccountSet() { return this.accountSet; }
void addAccount(Account account) {
AccountOwnership.add((Client)this, account);
}
void removeAccount(Account account) {
AccountOwnership.remove((Client)this, account);
}
}

Figure 5.10: Implementation of the methods for a role with a multiplicity upperbound greater than one. The methods addAccount and removeAccount simply
call the kernel methods add and remove from the Associations instance.

5.7.3.3

Implementing the Association-Aware Set

According to the DML semantics, the set returned by the method getAccountSet,
shown in Figure 5.10, must be backed up by the association and the instance of the
class Client on which it was called. That is, changes in the set should give rise to
changes into the associations links and vice-versa. The class AssocSet, which I outline
in Listing 5.10, implements this semantics.
We create an instance of AssocSet, by passing it an object corresponding to an end
of a link and an instance of an Association. The set stores these objects internally,
and when an element is added to or removed from the set, it calls the kernel methods add
or remove of the association object with that element and the stored end of the link.
The association object, however, must be able to add and to remove elements of the
set, as part of the creation or removal of a link. Yet, calling again the method add (or

remove) of the set would lead to an infinite loop. Thus, the class AssocSet provides
an alternative interface for adding and removing elements: the methods justAdd and

justRemove. These methods effectively add or remove the element from the underlying
set: an instance of the class VSet, which is a transactional set provided by the JVSTM.
Obviously, these low-level methods are meant to be used only by the association class.

5.7 Implementation of a Domain Specification

public class AssocSet<E1,E2> extends AbstractSet<E2> {


private Set<E2> set = new VSet();
private E1 linkEnd;
private Association<E1,E2> assoc;
public AssocSet(E1 linkEnd, Association<E1,E2> assoc) {
this.linkEnd = linkEnd;
this.assoc = assoc;
}
void justAdd(E2 elem) {
set.add(elem);
}
void justRemove(E2 elem) {
set.remove(elem);
}
public int size() {
return set.size();
}
public boolean contains(Object o) {
return set.contains(o);
}
public boolean add(E2 o) {
if (set.contains(o)) {
return false;
} else {
assoc.add(linkEnd, o);
return true;
}
}
public boolean remove(Object o) {
if (set.contains(o)) {
assoc.remove(linkEnd, (E2)o);
return true;
} else {
return false;
}
}
...
}
Listing 5.10: Implementation of the association-aware class AssocSet.

129

130

Domain Modeling Language

5.7.3.4

Implementing the Association Class

All the methods shown above delegate into the association object the responsibility of
actually creating and removing links, in its add and remove methods, respectively.
To create or remove a link, these methods must perform several operations. For
instance, to create a link for a bidirectional association, we have to update both of the
objects that we want to link. Furthermore, in some cases, the creation of a link may
result in the elimination of previous links.
The exact sequence of operations depends on the multiplicities at both ends of the
association. So, we need to distinguish, at least, the three following cases: one-to-one,
one-to-many, and many-to-many associations. In fact, given that we need to update each
of the two objects of a link so that they point to the other object, these three cases result
from the composition of only two distinct ways of updating an end of a link: when we
have a single object, and when we have a set of objects.
Therefore, we may implement an association class in an entirely generic way, as
shown in Listing 5.11. The DirectAssociation class delegates the work of updating
the ends of a link to objects of the Role type. Creating different instances of this class,
with appropriate instances of the Role type, allows us to implement the various types of
associations.
Before we look at the Role type, note the use of the annotation Atomic to ensure that
all the operations affecting a link are executed atomically. With this implementation, the
code of each listener executes within the atomic action, also. So, if some listener throws
an exception, either before or after creating or removing a link, the entire operation is
aborted. This gives us an increased expressive power to develop the domain model.
Finally, for completeness, I show in Listing 5.12 an outline of the implementation
of the class InverseAssociation, which is used in the implementation of the class

DirectAssociation.

5.7.3.5

Implementing Different Role Types

The final piece to conclude the implementation of an association declaration is the implementation of the classes that update each of the associations ends. These classes must
implement the interface Role, shown in Listing 5.13.
An association object calls the method add to create a new link between o1 and o2.
The responsibility of the method add of the Role interface is to change the object o1,
only; that is, this method performs only half of the work. The assoc argument is the
association object that made the call. This object may be needed to remove an existing

5.7 Implementation of a Domain Specification

class DirectAssociation<C1,C2> implements Association<C1,C2> {


private Association<C2,C1> inverse =
new InverseAssociation<C2,C1>(this);
private List<AssocListener<C1,C2>> listeners = ...;
private Role<C1,C2> firstRole;
private Role<C2,C1> secondRole;
DirectAssociation(Role<C1,C2> first, Role<C2,C1> second) {
this.firstRole = first;
this.secondRole = second;
}
@Atomic
void add(C1 o1, C2 o2) {
for (AssocListener<C1,C2> l : listeners) {
l.beforeAdd(this, o1, o2);
}
firstRole.add(o1, o2, this);
secondRole.add(o2, o1, inverse);
for (AssocListener<C1,C2> l : listeners) {
l.afterAdd(this, o1, o2);
}
}
@Atomic
void remove(C1 o1, C2 o2) {
for (AssocListener<C1,C2> l : listeners) {
l.beforeRemove(this, o1, o2);
}
firstRole.remove(o1, o2);
secondRole.remove(o2, o1);
for (AssocListener<C1,C2> l : listeners) {
l.afterRemove(this, o1, o2);
}
}
Association<C2,C1> getInverse() {
return inverse;
}
...
}
Listing 5.11: Implementation of the generic class DirectAssociation.

131

132

Domain Modeling Language

class InverseAssociation<C1,C2> implements Association<C1,C2> {


private Association<C2,C1> inverse;
InverseAssociation(Association<C2,C1> inverse) {
this.inverse = inverse;
}
void add(C1 o1, C2 o2) {
inverse.add(o2, o1);
}
void remove(C1 o1, C2 o2) {
inverse.remove(o2, o1);
}
Association<C2,C1> getInverse() {
return inverse;
}
...
}
Listing 5.12: Implementation of the class InverseAssociation.

interface Role<C1,C2> {
void add(C1 o1, C2 o2, Association<C1,C2> assoc);
void remove(C1 o1, C2 o2);
}
Listing 5.13:

The generic interface Role.


This interface is used by the
DirectAssociation class to update each of the associations objects in a link.

5.7 Implementation of a Domain Specification

abstract class RoleOne<C1,C2> implements Role<C1,C2> {


void add(C1 o1, C2 o2, Association<C1,C2> assoc) {
if (o1 != null) {
VBox<C2> o1Box = getBox(o1);
C2 old2 = o1Box.get();
if (o2 != old2) {
assoc.remove(o1, old2);
o1Box.put(o2);
}
}
}
void remove(C1 o1, C2 o2) {
if (o1 != null) {
getBox(o1).put(null);
}
}
abstract VBox<C2> getBox(C1 o1);
}
Listing 5.14: The implementation of the class RoleOne.

link for the object o1, in the case when it cannot have more than one link. The case for
the method remove is similar, except that this method does not need the third argument.
We need two distinct implementations of this interface: one for when we have a role
with a multiplicity upper-bound of one, another for when the multiplicity upper-bound is
greater than one. The first class is called RoleOne, and is shown in Listing 5.14. The
second class is called RoleMany, and is shown in Listing 5.15.
These classes are abstract because, to perform the changes, they need to access either
the VBox or the AssocSet that holds the value that should be changed. The box or set to
access, however, is different from one association to another, which means that we need
a different subclass of either RoleOne or RoleMany for each role declaration. These
subclasses simply override the methods getBox or getSet to return the appropriate
object, given an object o1.
These latter subclasses are generated as anonymous inner classes for each role declaration. Note that, unlike these, none of the classes AssocSet, DirectAssociation,

InverseAssociation, RoleOne, and RoleMany are generated by the DML compiler.


Rather, they are part of a runtime library provided by the DML compiler to facilitate the
implementation of an association declaration.

133

134

Domain Modeling Language

abstract class RoleMany<C1,C2> implements Role<C1,C2> {


void add(C1 o1, C2 o2, Association<C1,C2> assoc) {
if ((o1 != null) && (o2 != null)) {
getSet(o1).justAdd(o2);
}
}
void remove(C1 o1, C2 o2) {
if ((o1 != null) && (o2 != null)) {
getSet(o1).justRemove(o2);
}
}
abstract AssocSet<C2> getSet(C1 o1);
}
Listing 5.15: The implementation of the class RoleMany.

5.7.3.6

Enforcing Multiplicity Constraints

In the implementation given above I have not addressed one question: How to guarantee that the multiplicities of an association are not violated? I have not addressed this
problem yet, because it is not possible to implement this requirement adequately until I
introduce the consistent predicates in the next chapter. I shall return to this topic again
in Section 6.5.

5.8

Related Work

To the extent of my knowledge, the approach that I propose in this chapter is novel, even
though there is plenty of work that addresses the same problem.
I propose a new language specifically designed for implementing a domain models
structure and to complement another general-purpose programming language. The key
idea is to allow the implementation of a domain models structure in a language that
is midway between the languages used to represent a domain model and the languages
used to implement them. This language, however, is fully operational, allowing a complete
implementation of a domain models structure via automated code generation.
In contrast with this approach, all the previous work on the subject of reducing the gap
between a domain model and its implementation fits into one of the following approaches:

Support associations directly as a first-class construct in the programming language, which is then used to implement both the structure and the behavior of a
domain model.

5.8 Related Work

Facilitate the implementation of associations in a programming language by providing libraries and patterns that capture the best-practices of implementing associations in that language.
Automate the generation of the code that implements part of a domain model, starting with the domain model expressed in a modeling language such as UML.

The last approach is the one that more closely relates to the work in this dissertation.
It is, however, worthwhile to discuss all of these approaches, given that all of them are
trying to solve the same problem.

5.8.1

Associations as First-Class Language Constructs

Proposals to add to object-oriented programming languages associations (or relationships)


as a first-class language construct date back to twenty years ago, to the work of Rumbaugh.
First in [Rumbaugh, 1987] and later in [Shah, Hamel, Borsari, and Rumbaugh, 1989],
Rumbaugh and colleagues argued for the inclusion of relations as a core construct of
object-oriented programming languages, on par with the constructs already available for
classes. In that work they describe an object-oriented programming language called DSM,
which extended the C language with constructs for objects and relationships, among other
things. In the DSM language, relations are declared with a top-level construct that gives a
name to the relation and identifies the classes that participate in the relation, associating
a role name and a cardinality with each participating class. Thus, apart from the syntax,
association declarations in the DML are very much alike relations in the DSM.
Moreover, declaring a relation between two classes in DSM generated access methods
in each of the participating classes, named after the participants roles, that allowed the
navigation from one object to those with which it related to. This approach to relationship
implementation is still the current practice in the object-oriented programming world. In
this area, the DML followed the common practice in the area to provide a language that
is familiar to the programming community.
Besides Rumbaugh, several other researchers proposed to add explicit relationship
constructs to their languages. For instance, Albano, Ghelli, and Orsini [1991] criticize
the usual approach of implementing associations between objects by adding properties
to each of the participants in the association. Instead, they propose a language for an
object-oriented database which offers constructs for representing explicitly associations,
as well as for establishing constraints on those associations.
More recently, in the context of the Java programming language, Bierman and Wren
[2005] proposed the language RelJ, which includes relationships as a first-class language

135

136

Domain Modeling Language

construct. RelJ is an extension of a small subset of the Java programming language,


so that the authors could formalize the language and prove certain safety properties on
the relationships types. In RelJ, not only relationships are first-class entities, but also
their instances are. Also, relationships may have fields and methods, may extend other
relationships, and may participate in other relationships.
The various approaches to support relationships at the programming language level
vary considerably in the details. Whereas some (as the DSM of Rumbaugh) support
arbitrary n-ary relations, others support binary relationships only (RelJ, for instance).
Also, some support qualified relations, whereas others do not. Studying and discussing
all those variations, however, is beyond the scope of this dissertation.

5.8.2

Patterns for Implementing Associations

Even though, theoretically, adding support for relationships to the programming language
is the best approach, practically, these proposals are of little help if they never reach
mainstream programming languages.
Another, more practical route, is to stay within the limits of current programming
languages, and see how we can implement associations more easily. I call this approach
the patterns approach, even if not all the proposals may qualify as patterns (in the sense
of [Gamma et al., 1995]).
In [Noble, 2000], Noble proposes a set of basic relationship patterns for an objectoriented programming language. He describes several patterns to implement relationships, from the simplest Relationship As Attribute, which implements a small, simple,
one-to-one unidirectional relationship, to the more complex Mutual Friends, which implements a bidirectional relationship.
The patterns described by Noble capture the common practices of implementing relationships in object-oriented programming languages. In fact, the examples given in Section 3.3.2 follow some of these patterns. Likewise, either these patterns or some of their
variations are typically used to implement the higher level constructs provided by other
approaches. For instance, in the implementation of a DML domain specification I use
some of these patterns, as do other authors.
One of the problems of using most of the patterns described by Noble is that we lose
track of a relationship after it is implemented: Implementing a relationship, spreads the
code among several different classes, making it difficult to recover the relationship again
later [Guhneuc and Albin-Amiot, 2004; Gueheneuc and Albin-Amiot, 2003].
The pattern that resists better to this problem is the Relationship Object (or one of its
specializations, the Active Value, or the Collection Object), which uses a distinct object to

5.8 Related Work

137

represent the relationship, thereby encapsulating all the code in a single place. Several
authors have proposed solutions that are, in their essence, variations of this patternfor
instance, [Noble and Grundy, 1995; Suscheck and Sandn, 2003].
A more recent approach by Pearce and Noble [2006] uses aspect-oriented programming (see [Kiczales, Lamping, Menhdhekar, Maeda, Lopes, Loingtier, and Irwin, 1997])
to implement relationships as a crosscutting concern. By leveraging on the facilities
provided by the Aspect/J language, the authors propose a Relationship Aspect Library
that allows programmers to represent relationships explicitly, separate from their participating classes. Although promising, this approach has still several shortcomings. For
instance, to have more than one relationship for a given class, the authors resorted to
the workaround of having several almost identical copies of the aspects that implement a
relationship. Obviously, this solution does not scale for any medium-sized application.
Moreover, the syntax for adding links to a relationship departs significantly from
the common practice on the object-oriented programming area. For instance, using the
example given by Pearce and Noble in their paper, programmers must write code such
as Attends.aspectOf().add(student, course) to create a new relationship link
between two objects, instead of writing the typical course.enroll(student) method
call (unless, of course, they write by hand the enroll method to call this code, which
defeats the purpose of the proposal).
This last issue is, in fact, a problem shared with many other approaches that make the
relationship a first-class object in the language. For instance, both the DSM language and
the RelJ language use this same approach to change a relationship. The DML language,
on the other hand, provides both ways of adding links to relationships. We may, thus, use
either the code Course.Attends.add(course, student) to add a new link to the

Attends association, or use the more standard call course.addStudent(student),


instead.

5.8.3

Generating the Code for Associations

Given that we have well-defined patterns for implementing an association, rather than
applying those patterns ourselves, we may automate their implementation instead. In
fact, this approach has been increasing steadily in popularity.

The advances in the

areas of generative programming [Czarnecki and Eisenecker, 2000] and model-driven


development [Selic, 2003] certainly contributed to this increase.
There is a wealth of tools, both free and commercial, that generate automatically code
for fragments of the UML language.3 With code generation, however, comes the problem
of avoiding round-tripping, which many tools fail to address.
3

For a comparison of 10 such tools, see [Akehurst, Howells, and McDonald-Maier, 2007].

138

Domain Modeling Language

Avoiding round-tripping is one of the ten objectives set up by Harrison et al. [2000],
in their work on mapping high-level UML designs to Java. Harrison and his colleagues
propose a new method for generating Java code from a high-level design model expressed
in a UML class diagram. To avoid round-tripping problems, they split the implementation
of a class into two classes plus an interface, so that one of the classes contains the code
generated and the other is for the programmer make changes. The semantics of entity
type declarations in the DML language borrow from this, even though it uses a simpler
(and better) approach as it dispenses the interface.
A significant part of [Harrison et al., 2000] is dedicated to the implementation of associations. Unlike in the DML, which strives for using common programming idioms, in this
work the authors propose the use of cursors to manipulate an association. Cursors are
similar in spirit to the association-aware set described in the semantics of associations
declarations (see Section 5.6.2.4). Yet, whereas the association-aware set of DML implements the Javas standard Set interface, cursors provide an entirely new and rather more
limited interface. Moreover, the only way to change an association is through a cursor,
which, again, clashes with the common practice in the area.
Gnova et al. [2003], on the other hand, propose also a mapping for a fragment of
the UML class diagrams, but instead of cursors, they follow a more traditional approach
of adding methods to the classes in each end of an association. Yet, it is not clear from
their paper how they distinguish among different associations for the same class. Also,
they describe the interface of the code generated for an association, but do not specify the
semantics precisely; for instance, they do not say whether the collection returned by the
getter of an association end is mutable or not. No implementation is described either.
Finally, the most complete treatment of the UML language that I am aware of was
published recently by Akehurst et al. [2007]. In this work, Akehurst and his colleagues
describe thoroughly code generation patterns for each one of the possible variations on a
UML 2.0 association. They stumbled upon some difficulties, however. For instance, how
to enforce the minimum multiplicities of a bidirectional association, for which they have
no good answer. As we shall see in Section 6.5, the DML compiler will be able to solve
this problem.
To conclude this discussion of related work, I point out that in none of these proposals
is it clear whether the programmer may customize the behavior of the code generated for
an association.

5.9

Summary

This chapter describes a new languagethe DML languagethat purports to reduce the
gap that currently exists between a domain model and that domain models implementa-

5.9 Summary

139

tion. Specifically, in what regards the implementation of a domain models structure.


Unlike current object-oriented programming languages, the DML language provides
constructs for representing both entities and associations, thereby allowing a more direct implementation of a domain model that uses a modeling language with similar
constructsfor instance, UML.
The syntax and the semantics of the DML language are described thoroughly, giving,
thus, a precise specification to the language and making it fully operational.
Within its scope, the DML language is arguably a higher-level language than the
currently available object-oriented programming languages. The DML, however, is not a
general-purpose programming language, given that it allows only the representation of
a domain models structure. In fact, it was designed to integrate seamlessly with the
Java programming language; this design decision is visible both in the syntax and in the
semantics of the language.
The integration of DML with the Java language is accomplished through a process of
code-generation that transforms a DMLs domain specification into Java code. Some of
the requirements for the generated code are embedded into the DML semantics, but the
exact code to generate is not part of the language specification, thereby allowing different
strategies for implementing a domain model.
Nevertheless, this chapter describes how to transform a domain specification into a
transactional domain model implementation, by describing which Java code is generated
for each of the languages constructs.

This implementation leverages on the JVSTM

described in the previous chapter, and is sufficiently simple and readable to constitute
a good design pattern for implementing associations in Java, even when the DML is not
used.
Finally, the DML language, when compared to the alternatives, provides some distinctive characteristics:

The code generated from a domain specification has a programmer-friendly interface


that adheres to the current practices used in object-oriented programming.
Even though the implementation of an association is entirely generated, the programmers may still customize the behavior of the generated code without hampering
the round-tripping of the system.
The implementation of an association integrates well with the existing Java Collections Library, which gives to the programmers the freedom to use the interfaces that
they are already used to.
By implementing a domain model in a DML domain specification, programmers

140

Domain Modeling Language

create a valuable intermediate artifact with a precise semantics that may be used
for other purposes in the application.
Given its similarity with Java and its simplicity, the DML language is easy to learn
and to use by current Java programmers.

Chapter 6

Consistency Predicates
In the previous chapter, I proposed to separate the implementation of a rich domain model
into the implementation of the domain structure and the implementation of the domain
behavior. But, whereas to implement the structure of a domain model I proposed a new
language, to implement the domains behavior I propose that programmers continue to
use Java; the DML language proposed in the previous chapter was specially designed to
allow this separation.
Java is a widely used general purpose object-oriented programming language, with
a reasonable set of programming constructs, and, more importantly, with an immense
set of libraries, tools, and documentation that greatly simplify the task of developing new
applications. Therefore, by using Java as an implementation language, we may take
advantage of all the facilities available for it. Unfortunately, the implementation of a rich
domain model in Java is not exempt of problems, as the examples discussed in Chapter 3
illustrate.
One of the difficulties found in the implementation of a domain model was in implementing domain constraints, as the example of implementing the limit imposed on the
total balance of a banks client that was discussed in Section 3.4.2. Yet, even though
this is a single example, the problems that we found in that example are not uncommon. Quite on the contrary, that example is representative of a frequent problem in the
implementation of rich domain models: How to ensure that the domain constraints are
maintained during the execution of the program?
The common approach to this problem is to implement the methods that may change
the state of domain objects with great care, to ensure that the domain remains in a
consistent state.

In this chapter, I propose a new approachthe use of consistency

predicates. Consistency predicates are a powerful construct that allows programmers to


express the conditions for domain consistency separately from the methods that change
the state of domain objects. Then, at commit time, these consistency predicates are used

142

Consistency Predicates

to validate the execution of atomic actions. By leveraging on the mechanisms needed to


support STMs in a programming language, I show that consistency predicates may be
introduced in Java with little additional effort.
In the following, I start with a discussion on the difficulty of ensuring the consistency
of a domain model. Then I introduce the notion of consistency predicates and show
how they may be used to simplify the implementation of domain consistency. Finally, I
describe how consistency predicates are implemented in the JVSTM.

6.1

Domain Consistency

One of the basic rules of good object-oriented programming, taught in most books on
the subject, is that each object is responsible for maintaining the consistency of its own
state. To enforce this rule, the state of an object should not be accessible to other objects.
Rather than changing an objects state directly, other objects call the methods provided by
that object to perform the changes they need. Each of the methods provided by an object,
in turn, must ensure that it leaves the object in a consistent state after its execution.
Making an object the sole responsible for its own state is a good design rule because
it limits the places in the code that need to have knowledge about the constraints for that
object, independently of the context where the object is used. Therefore, by following this
design rule, we increase the modularity of an object-oriented program.
Yet, whereas following this rule may be possible for isolated objectswhich do not have
relationships with other objects or that have relationships only with owned objectsit is
not possible, or at least convenient, in general, for rich domain models.
Before I explain the reasons why rich domain models are special in this regard I
discuss first the case of maintaining the consistency of a single objects state.

6.1.1

Consistency of Single Objects

Consider the typical example of a buffer with a maximum capacity. A common implementation for such a buffer object is to maintain information in the object about its maximum
capacity, the number of elements that it contains, and the contained elements. Given
this implementation, the constraints that the state of every buffer object must verify are:
(1) that the number of elements in the buffer is between zero and the buffers maximum
capacity, inclusive; and (2) that the element count is equal to the number of elements
actually contained in the buffer.
Note that the constraints for a buffer object depend only on the buffer objects state.
Thus, we can be sure that these constraints remain valid throughout the entire lifetime

6.1 Domain Consistency

of a buffer object, if we enforce two things.


First, that whenever a new buffer object is created, it is created in a consistent state.
For instance, a constructor for a buffer object may receive a non-negative integer value as
argument and return a new buffer object with that value as its maximum capacity, with
an element count of zero, and with no elements in it. A buffer object with such a state is
consistent, as it satisfies all the constraints stated above.
Second, that each of the methods that changes the state of a buffer object leaves the
object in a consistent state after the methods execution, provided, of course, that the
object was in a consistent state before the method was called. An example of a method
that may change a buffer object is the method to add an element to the buffer. This
method should add the element to the buffer and increment the element count by one.
Failing to do one of the two things would leave the buffer object in an inconsistent state
and is a programming error. Yet, sometimes doing both things leaves the object in an
inconsistent state, also: When the number of elements in the buffer is equal to the buffers
maximum capacity, adding another element violates the first constraint presented above.
So, the method to add an element to a buffer should check for this case and return to
the caller an indication that the operation could not be performed if the buffer is full.
Because in Java it is usual to throw an exception in such cases to indicate a failure,
in the following I shall assume that a method throws an exception whenever it cannot
perform the requested operation.
This simple example illustrates a common pattern for the methods that change the
state of an object. These methods must check whether they may perform the operation
requested, throwing an exception if not. Otherwise, they perform the operation, updating
the state of the object such that the object remains in a consistent state in the end.
Nevertheless, even though the object must be consistent in the end of an operation,
it may be temporarily inconsistent during the execution of the operation. For example,
the method to add an element to the buffer, either increments first the element count
and then adds the element to the buffer, or it does these two things in the reverse order.
In either case, the state of the object becomes inconsistent between the two operations.
That inconsistency, however, must be eliminated before the method returns to its caller.1
Finally, note that not all of the objects methods need to leave the object in a consistent
state. Only the public methodsthat is, methods that other objects may callneed to
ensure that. The internal methods of the object, which may be used only as helper
functions by the remaining methods of the object, do not need to satisfy this requirement.

1
The problem, of course, is worse when we have concurrent accesses to the object that may view that
intermediate state. Possible solutions to this problem include using locks or atomic actions, and were
already discussed in Section 3.1 and in Chapter 4my preference, obviously, going to the use of atomic
actions.

143

144

Consistency Predicates

6.1.2

Consistency of Rich Domain Models

A distinctive aspect of rich domain models is that many of the constraints for a rich
domain model involve the state of more than one entity, rather than the state of a single
entity as in the example of the buffer object presented above.
For instance, the constraint implied by a bidirectional association between two different entity types, A and B, is a trivial (and common) example of a constraint that involves
more than one entity: If an instance of A has a reference to an instance of B, then that
instance of B must have a reference to that same instance of A. Moreover, depending on
the multiplicities of the association, there may be further constraints for the relationship
between instances of both types.
The problem of having constraints that depend on the state of more than one object,
however, is that it is no longer easy to localize the implementation of those constraints in
one single object. If a constraint refers to the state of more than one object, then we must
ensure that, whenever a change occurs in the state of any of those objects, the new state
after the change still satisfies the constraint. Unfortunately, the approach of checking the
constraint at each method that may affect the constraints validity brings with it several
problems.
First, it means that, to implement the constraints for a rich domain model, programmers must reason about several classes simultaneously, rather than concentrating on
only one. One of the advantages of object-oriented programming is that it reduces the
complexity of the programming task by allowing programmers to concentrate on the implementation of each class in turn, ignoring the implementation details of other classes.
But, when the implementation of a constraint for a class depends on the state of objects
from other classes, programmers must take into consideration the implementation of all
those other classes, also. In particular, they may have to change the other classes methods to ensure that the constraint is not violated. Yet, having several methods to worry
about is error-prone, because programmers may easily forget about one of those methods, thereby opening the door to domain inconsistencies. Often, such errors are difficult
to detect, either because the constraint is not explicitly stated anywhere in the code, or
simply because the constraint is so complex that it is not clear which are the objects
that it depends on. Furthermore, the probability of occurring such a programming error
increases when the code needs to be changed as a result of some requirements change.
Second, this approach leads rapidly to modularity problems such as code scattering,
code tangling, and strong coupling among the domain classes. Code scattering occurs
because the implementation of each constraint is spread over several methods of possibly
different classes. Code tangling, on the other hand, occurs when different constraints
refer to the state of the same entity typein this case, the methods of the shared entity
type must have code to check each of the constraints, thereby mixing several concerns in

6.2 Examples of Constraints

a single method. The strong coupling among the domain classes occurs because each of
the entities referred to by a constraint needs to be aware of the other entities, so that it
can check the constraint when its state changes. A possible solution to these modularity
problems is the systematic use of the observer pattern [Gamma et al., 1995] to register
dependencies between entities. Yet, that solution complicates the domain implementation
enormously. Thus, in this dissertation I strive for a better solution.
Finally, there is a more fundamental problema problem of compositionwith the
approach of checking the constraints for a rich domain model at each of the domain entity
types methods: In many cases, those methods are used as part of larger operations, and,
thus, they may need to break the domains constraints temporarily during the operation.
Consider, for example, the case of establishing a new bidirectional link between two
objects. After one of the objects is changed to point to the other, the domain is not
in a consistent state until the latter object is changed to point to the former object.
Therefore, the method that changes the state of the former object cannot check whether
the constraint affected by that change is violated or not; it must assume that the method
that calls it ensures the final consistency of the domain. In fact, these methods are like
the private methods of the single object case, which may break the constraints for an
object. But, whereas in the case of single objects, the callers of the private methods are
necessarily from the same class, in the case of a rich domain model, the callers of the
methods that are part of larger operations must be, often, from other classes. So, it is
much harder to ensure that all such callers ensure the domain consistency, because they
are not confined to a single class; in fact, in many cases, the callers are not limited in any
way. Moreover, to ensure that, in the end, they leave the domain in a consistent state, the
higher-level operations that call the lower-level operations must have knowledge about
the details of the objects they affect, thereby breaking the object modularity.
Therefore, the composition of two or more objects into new collaborations is difficult
with current object-oriented practices; specially when the constraints of the composed
objects may need to be violated temporarily during the execution of a composing operation.

6.2

Examples of Constraints

The problem associated with the difficulty of composing domain objects was already discussed in Section 3.4.2. To exemplify the remaining problems with concrete examples,
I shall discuss, in the following, the implementation, using a traditional approach, of
several constraints taken from the same banking domain which was introduced in Section 2.2.2. Each of these examples illustrates some of the problems with the traditional
approach of implementing constraints in a rich domain model. Then, in the next section,
I present the consistency predicates, which allow a much simpler implementation of the
same constraints.

145

146

Consistency Predicates

Clients with an active checking account


As a first example, consider the functionality requirement that all the banks clients must
have at least one active checking account. Note that this constraint is not represented in
the structure of the domain model because the design that I chose as a solution for the
banking domain has an association between the classes Client and ClientAccount,
which includes both checking and savings accounts. So, having a multiplicity value of

1..* in the role declaration for the ClientAccount is not enough to guarantee that
each client has a checking account.
Starting with the implementation of the constructor for the class Client, must a new
client, as returned by the constructor, have already a checking account? If the returned
object must have a consistent state, the answer to the previous question is yes, and we
may implement it in one of two ways:

The constructor receives a non-closed checking account as an argument and adds


that checking account to the clients set of accounts. In this case, however, the
checking account must already exist before the call to the Clients constructor.
So, it must have been created without an owner, which violates the constraint that
all the client accounts must have exactly one owner.
The constructor for the class Client creates a new checking account by calling
the CheckingAccount constructor with the instance of the client as the owner of
the account to create. This solution allows that the constructors for both classes
return consistent instances. Yet, it is a little bit awkward that the responsibility
of creating a checking account is in the client, rather than in the bank, because,
most probably, there are restrictions for the creation of an account that should be
verified by the bank.

This difficulty of ensuring the consistency of a newly constructed object is amplified


if we consider cases where we need to create several objects that must form a complex
graph. Neither of the two previous solutions scales well for such cases. In general, it is
easier to have some method that knows how to create all the objects and how to link them
together.
So, if we take that route and we decide that the constructor for the class Client does
not need to return a client with a checking account, where else is the responsibility of
ensuring the consistency of the new client object? Most probably, the creation of a new
client, the creation of a new checking account, and the linking of the two objects, should
be the responsibility of some method in the class Bank. Unfortunately, even though such
a method in the class Bank may ensure the consistency of the clients it creates, it is not
easy in Java to ensure that clients are created only by this method. Thus, by allowing
the creation of inconsistent clients, we open up the door for having at another part of the

6.2 Examples of Constraints

application code the creation of an instance of the class Client without ensuring the
proper constraints for the client objects.
Either way, regardless of the solution chosen for the construction of the client object,
then we have the problem of avoiding that future changes to the state of the objects violate
the consistency of the client object. Which changes may do that? Either the removal of
an account from the set of accounts of a client, or the closing of a checking account.
Therefore, we must specialize both of these operations to ensure that the client remains in a consistent state when each of the operations is performed. If the normal
execution of an operation would cause an inconsistency, then the operation must fail
by throwing an exception. In Listing 6.1 on the following page, I show a possible implementation of this functionality. Note that, even though I created a method in the class

Client to check the constraint, that method must be called from several locations in the
code, both in the class Client and in the class CheckingAccount.
This implementation suffers from the problem of code scattering, because the code
that implements the constraint is scattered over various methods of several different
classes. Furthermore, given that the method close of the class CheckingAccount
needs to call the method checkActiveCheckingAccountExists in the class Client,
it creates a stronger coupling between these two classes.

Closed accounts
The second example is about the constraints associated with closed accounts. In the
description of the example given in Section 2.2.2, there are several functionalities related
to closed accounts: only classes with a balance of zero may be closed; closed accounts
cannot have further deposits or withdrawals; and once the balance of a savings account
reaches zero, the account must be closed.
In this case, because the constraints depend only on the state of one object (a client
account), the implementation of these requirements is relatively straightforward:

We must check in the method close of the class ClientAccount that the balance
of the account is zero, throwing an exception if not.
We must override, in the class ClientAccount, both of the methods deposit and

withdraw to throw an exception when the account is closed. Or, in alternative,


we may override only the method setBalance to throw an exception in the same
condition.
We must override the method withdraw (or the method setBalance) in the class

SavingsAccount to close the account when the balance reaches zero.

147

148

Consistency Predicates

class Client extends ClientState {


void checkActiveCheckingAccountExists() {
for (ClientAccount acc : getAccountSet()) {
if (acc.isChecking() && (! acc.isClosed())) {
return;
}
}
throw new ClientWithoutCheckingAccountException();
}
static {
AccountOwnership.addListener(new CheckListener());
}
static class CheckListener
extends AssocAdapter<Client,ClientAccount> {
void afterRemove(Client client, ClientAccount acc) {
client.checkActiveCheckingAccountExists();
}
}
}
class CheckingAccount extends CheckingAccountState {
@Atomic void close() {
super.close();
getOwner().checkActiveCheckingAccountExists();
}
}
Listing 6.1: Ensuring that a client has always at least an active checking account. The class AssocAdapter is a generic class that implements the interface
AssocListener with all the methods empty. It is used to simplify the creation of
listeners that need to specialize only one of the methods.

6.3 Consistency Predicates for Atomic Actions

class ClientAccount extends ClientAccountState {


@Atomic void close() {
if (! getBalance().isZero()) {
throw new CloseAccountWithMoneyException();
}
setClosed(true);
}
void setBalance(Money newBalance) {
if (isClosed()) {
throw new ClosedAccountException();
}
super.setBalance(newBalance);
}
}
class SavingsAccount extends SavingsAccountState {
@Atomic void setBalance(Money newBalance) {
super.setBalance(newBalance);
if (newBalance.isZero()) {
close();
}
}
}
Listing 6.2: Implementation of the constraints for closed accounts.
In Listing 6.2, I show the methods of the class ClientAccount and of the class

SavingsAccount that implement these three changes. Unlike in the previous example,
the code that implements each constraint is localized in the appropriate class, rather than
being spread over different classes.
Yet, note that each of the methods shown in this example mixes code from different concerns, rather than having each concern implemented separately. It is, thus, an
example of code tangling.

6.3

Consistency Predicates for Atomic Actions

To simplify the implementation of constraints for a rich domain model, I propose the use
of consistency predicates. The two key ideas underlying consistency predicates are the
following:
A consistency predicate is a predicate that checks whether a domain object satisfies
some particular constraint.
Consistency predicates are checked only at the end of an atomic action, rather than

149

150

Consistency Predicates

A
value:int

0..1
a

0..*
b

B
value:int

Figure 6.1: UML class diagram for two classes with a bidirectional association between them.
being checked whenever a method of a domain class is executed; the atomic action
is valid only if all the consistency predicates evaluate to true.
Even though the constraints for rich domain models may involve the state of more
than one object, I assume that there is always one object through which we can access all
the objects needed to check the validity of a constraint. Thus, we may check the validity
of all the constraints for a rich domain model with predicates for single objectsthese
predicates are the consistency predicates.
Note that, even though consistency predicates are predicates for single objects, they
may refer to other objects that are accessible through the object that is being checked by
the predicate. In fact, I propose that consistency predicates be implemented as domain
classes methods, with no restrictions other than that they must return a boolean value
and that they should have no side-effects.
To show an example of methods that may implement consistency predicates, consider
the case of two classes, A and B, with a bidirectional association between them, as depicted
in Figure 6.1. We may implement these classes in DML as follows:

class A { int value; }


class B { int value; }
association Rel {
A playsRole a { multiplicity 1..1; }
B playsRole b { multiplicity 0..*; }
}
Consider, also, that there are two constraints involving these classes: (1) the value of each
instance of class B must be less than half the value of the instance of class A with which
the instance of B is related to; and (2) the value of an instance of class A must be greater
than or equal to the sum of the values of all the instances of class B that are related to
that instance of A. In Listing 6.3 on the next page, I show how the consistency predicates
corresponding to these two constraints may be implemented as methods of the classes A
and B.
Consistency predicates are meant to ensure that the state of a domain model is kept
consistent. The domain is in a consistent state if all the domains objects satisfy all
the consistency predicatesfor example, in the case just presented, all the instances of
classes A and B must satisfy the two consistency predicates implemented by the methods

6.3 Consistency Predicates for Atomic Actions

class A extends AState {


boolean sumOfBsNotGreaterThanValue() {
int sum = 0;
for (B b : getBSet()) {
sum += b.getValue();
}
return getValue() >= sum;
}
}
class B extends BState {
Boolean valueIsLessThanHalfTheValueOfA() {
return getValue() < (getA().getValue() / 2);
}
}
Listing 6.3: Methods implementing the consistency predicates for the classes A and
B. Each method returns the value true if the receiving object satisfies the constraint
and return the value false otherwise.

shown in Listing 6.3. So, we may check whether the domain is in a consistent state by
calling each of the methods implementing consistency predicates for all the applicable
domain objects. But, when should we do this check?
If we assume that we start with a domain in a consistent statethat is, where all
the consistency predicates evaluate to truethen the only way to break the domain
consistency is by changing the state of any domain object. So, we just need to check
the consistency of the domain state when something changes in that state. Yet, as we
discussed in the previous section, it is not possible to have the domain in a consistent state
all the time because, to perform certain operations, we may need to break the consistency
temporarily, during the execution of the operation. So, I argue that the boundary for
enforcing the domains consistency should not be at the end of each method that may
change the state of a domain object. Rather, it should be at the end of each atomic
action, because they correspond to the smallest unit of work semantically meaningful in
a domain model. In fact, when using a language with atomic actions, the state of the
domain changes only as the result of some successful atomic action.
Atomic actions, as implemented, for instance, in the JVSTM, give us already the
properties of atomicity and isolation. By requiring that at the end of each atomic action
all the consistency predicates be true, we enforce also the property of consistency for
atomic actions, thereby ensuring that the domain will always remain in a consistent
state.
Thus, if at the end of an atomic action, any consistency predicate evaluates to false,
the atomic action cannot commit successfully. Instead, the atomic action must abort,
because it is not consistent. Note that consistency predicates cannot transform an incon-

151

152

Consistency Predicates

sistent action into a consistent one, because consistency predicates cannot change the
state of the domain. Consistency predicates only check the consistency of the domains
state, preventing inconsistencies by vetoing the commit of the actions that would cause
an inconsistency.
Having a complete and correct set of consistency predicates helps in the implementation of a rich domain model because any programming error in the update of the domain
state that would cause an inconsistency is detected by the consistency predicates. Detecting programming errors, however, is not the sole purpose of consistency predicates.
On the contrary, consistency predicates implement also the domain logic that is typically found in the methods that change the state of a domain objectthe domain logic
that checks whether the operation can be performed in the current state of the object
and with the given arguments. By using consistency predicates much of that code may
disappear from those methods. Instead, the methods perform the operations as usual
and if a constraint is violated, consistency predicates detect that violation and abort the
atomic action, thereby undoing all the changes that caused the inconsistency. This combination of atomic actions with consistency predicates represents a significant change
in how programmers may develop a rich domain model: Rather than using a defensive
style of programming that checks first whether the programs execution may proceed with
the changes requested, they may, instead, specify separately the constraints in consistency predicates and perform the actions without checking first, knowing, however, that
if something goes wrong all the changes will be undone.

6.4

Consistency Predicates in Java

To express consistency predicates in Java, I propose the use of a new annotation type,

@ConsistencyPredicate, that may be used to annotate the methods that check the
consistency predicates conditions. These methods must be non-static, have a return
type of boolean, and have no arguments. Also, because these methods are meant to
check that an object of their class satisfies the consistency predicate, they should be
made final so that they are not overridden in a subclass.
The code of the methods should return the boolean value true if the condition of the
consistency predicate is verified, and false otherwise. To determine its return value,
the method may execute whatever it wants, provided that it has no side-effects.
If the execution of a consistency predicate returns false, or fails in any way, the
transaction executing that consistency predicate is aborted, throwing an exception of
type ConsistencyException. The annotation ConsistencyPredicate, however,
may specify a different exception type to throw. In that case, the exception thrown by the
transaction is of the type indicated in the annotation.

6.4 Consistency Predicates in Java

class Client extends ClientState {


@ConsistencyPredicate(NegativeClientBalanceException.class)
boolean totalBalanceNonNegative() {
return (! totalBalance().isNegative());
}
}
Listing 6.4: Consistency predicate for checking that the client total balance is not
negative. The exception to throw if the consistency predicate fails is of the type
NegativeClientBalanceException.

class Client extends ClientState {


@ConsistencyPredicate
boolean atLeastOneCheckingAccount() {
for (ClientAccount acc : getAccountSet()) {
if (acc.isChecking() && (! acc.isClosed())) {
return true;
}
}
return false;
}
}
Listing 6.5: Consistency predicate for checking that a client always have a nonclosed checking account. The method getAccountSet returns the set with all
the clients accounts. The method isChecking returns true if the account is a
checking account and false otherwise.

In Listing 6.4, Listing 6.5, Listing 6.6, Listing 6.7, and Listing 6.8 on the next page, I
show the implementation of the constraints discussed above for the banking domain.
Contrary to the implementations discussed before, the use of consistency predicates
allows us to have all the constraints implemented in the appropriate class, in a single
code unit (a method) that deals with one and only one consistency predicate. So, we
no longer have code tangling or code scattering for the implementation of constraints
that refer to several domain entities. The coupling among the classes is reduced also,
given that only the class that wants to enforce a constraint needs to know about that

class ClientAccount extends ClientAccountState {


@ConsistencyPredicate
boolean closedAccountHasNoMoney() {
return (! isClosed()) || getBalance().isZero();
}
}
Listing 6.6: Consistency predicate for checking that a closed ClientAccount has no
money.

153

154

Consistency Predicates

class SavingsAccount extends SavingsAccountState {


@ConsistencyPredicate
boolean accountWithNoMoneyMustBeClosed() {
return (! getBalance().isZero()) || isClosed();
}
}
Listing 6.7: Consistency predicate for checking that a SavingsAccount with no money
must be closed.

class CheckingAccount extends CheckingAccountState {


@ConsistencyPredicate
boolean accountWithNegativeBalanceMustBeInBank() {
return getBalance().isNegative() == hasBank();
}
}
Listing 6.8: Consistency predicate for checking that a CheckingAccount is in the list
of accounts to process by the bank if and only if it has a negative balance.

constraint. Finally, given that the execution of the consistency predicates is not statically
determined in certain points of the code, it is now possible to compose several classes into
more coarse-grained objects with operations that orchestrate several of the finer-grained
classes operations without the need to tweak the methods being composed.

6.5

Enforcement of Multiplicities with Consistency Predicates

Besides all the remaining advantages described above, consistency predicates allows us,
also, an elegant solution to the much debated problem of implementing the constraints
imposed by the multiplicities of an association between two classes.
In fact, none of the work that I am aware of that tackles the problem of implementing
associationseither by extending the programming language, or by presenting patterns
on how to implement themaddresses conveniently the problem of enforcing the associations multiplicities. In may cases, this problem is simply ignored, whereas in other cases
the problem is acknowledged but no convincing solution is presented to solve it.
Likewise, when I described the implementation of a DML specification in Section 5.7, I
skipped over the implementation of multiplicities, but referred to this section as presenting a solution for it.
The difficulty in implementing the constraints imposed by the multiplicity property on
an associations role is not in implementing the code that checks whether the constraint
is verified; that is trivial, in fact. Rather, the problem is in knowing when to do that check.

6.6 Implementation of Consistency Predicates in the JVSTM

class ClientAccountState extends Account {


@ConsistencyPredicate
final boolean checkMultiplicityOfOwner() {
return hasOwner();
}
}
class ClientState {
@ConsistencyPredicate
final boolean checkMultiplicityOfAccount() {
return (getAccountSet().size() >= 1);
}
}
Listing 6.9: Consistency predicates generated by the DML compiler for checking the
multiplicity of the AccountOwnership association.

Consider, for example, that we want to enforce the multiplicities for the following
association (shown previously in Figure 5.8):

association AccountOwnership {
Client playsRole owner {
multiplicity 1;
}
ClientAccount playsRole account {
multiplicity 1..*;
}
}

Checking that the multiplicities are correct for an object of any of the classes is just a
simple matter of checking that the client account has an owner, or that the client has at
least one account. Yet, where and when should that check be made? The answer to this
question proves difficult when no notion of atomic action exists.
With consistency predicates, however, we may now use the multiplicity information
specified in the DML language to enforce, at the end of each atomic action, that domain
objects have the correct number of links as prescribed by an association declaration.
In Listing 6.9, I show the consistency predicates generated by the DML compiler for
checking the multiplicity of the AccountOwnership association.

6.6

Implementation of Consistency Predicates in the JVSTM

The implementation of consistency predicates in the JVSTM is relatively straightforward.

155

156

Consistency Predicates

We saw already that to use consistency predicates in a Java program we use the
method-level annotation @ConsistencyPredicate. Thus, finding all the consistency
predicates for a particular set of classes is just a matter of using the reflection capabilities
of the Java language to iterate over all the methods of all those classes and collect all
the methods found with this annotation. This, obviously, is needed only once for each
program execution, provided that we cache the result somewhere.2
Having the set of consistency predicates for a program, we need now to know when to
call them to check the consistency of the domain. Given their semantics, we know that
they must be called at transactions commit-time. Yet, not all transactions need to check
the consistency. Because only write-transactions can change the state of the domain,
only these transactions need to check the consistency predicates.
So, a first solution to the problem is to check all the consistency predicates for all the
objects belonging to classes with consistency predicates whenever a write transaction is
about to commit.
Even though this solution is simple to implement, it is unacceptably inefficient in
terms of time. We may do better in terms of execution time if we spend some memory to
keep a dependence network between the objects.
The fundamental idea is that, at the end of a transaction, we should check only the
following:
The consistency predicates of all the objects created during the transaction.
The consistency predicates that may have become invalid because of a change made
in some existing transactional locationthat is, a change in a VBox.
To be able to do this, transactions need to know when a new object is created and which
consistency predicates depend on each box.
The first part is easily implemented by adding a new method to the Transaction
interface that allows new objects to notify the transaction about their creation, which they
should do in their constructors (if we are using the DML to generate the domain classes,
this is easily automated).
To implement the second part, however, we need to keep a record of which consistency predicates depend on which boxes.

This is the purpose of both the class

DependenceRecord and the association shown in Figure 6.2.


When a consistency predicate is executed for the first time for a new object, a new
instance of a DependenceRecord is created and is initialized with the new object being
2

There are many other ways to implement this; for some we may even do all the work at compile time. Yet,
as this is relatively straightforward, I do not discuss all the alternatives here, so that I may concentrate on
the most relevant aspects of the implementation.

6.6 Implementation of Consistency Predicates in the JVSTM

VBox

0..*
depended

0..*
dependence

DependenceRecord
dependent:Object
predicate:Method

Figure 6.2: UML class diagram showing the central elements for the implementation
of consistency predicates in the JVSTM.
checked (which is kept in the dependent attribute) and with the Javas method that
implements the consistency predicate (which is kept in the predicate attribute).
Then, to execute the consistency predicate for the new object, a new special kind of
nested transaction that knows about this dependence record is created. This special
transaction differs from a normal nested transaction in the following:
If a write to a box is attempted, the transaction throws an exception. This prevents
that consistency predicates have side-effects (at least on the transactional state).
Whenever a box is read during the execution of the consistency predicate, the transaction should record that the consistency predicate depends on the value of that
box. So, it creates a link between the dependence record and the box.
Therefore, at the end of the execution of the consistency predicate, the dependence
record is related to the set of all the boxes on which the consistency predicate validity
depends. As the association between the class DependenceRecord and the class VBox
is bidirectional, each vbox is related, also, to the set of all the dependence records that
depend on the value of the box.
When a top-level write transaction commits, it just needs to go through each of the
boxes in its write set and recheck all the dependence records that are registered with each
box. Rechecking a dependence record means reexecuting the method stored in the slot

predicate on the dependent object stored in the slot dependent. This reexecution is
similar to the first one, when the dependence record is initially created, except that we
need to clear all the existing depended values for the dependence record (which has the
side-effect of removing the dependence record from the boxes on which it depended upon,
also) before the reexecution of the consistency predicate.
To conclude this outline of the implementation of the consistency predicates in the
JVSTM, there is one final question to be answered: At which phase during the commit of
a write transaction is the consistency check performed?
To answer this question it is worth noting that the process of checking the consistency
predicates for one particular transaction may occur concurrently with a similar process
for another transaction. Moreover, during the execution of this process, some shared
data structures (namely, the links between boxes and dependence records) are changed.
So, we need to be careful in implementing this process to avoid problems with data races.

157

158

Consistency Predicates

Consider, for instance, that we have a single dependence record, DR, that depends
on two boxes, B1 and B2. Imagine now that two concurrent transactions, T1 and T2,
are committing at the same time, and that T1 changed box B1 and T2 changed box B2.
Then, both transactions should recheck the dependence record DR. If both are doing
the consistency check concurrently, however, the following series of events may happen:
transaction T1 identifies DR within the dependencies of B1 and prepares to recheck it,
clearing all the DR depended valuesas a consequence of this, box B2 is no longer related
to DR; then, transaction T2 checks box B2 to see which dependencies exist for this box
and finds no dependence; finally, transaction T1 rechecks DR and, while doing it, adds
again the dependence to box B2. This example indicates that we may fail to reexecute
some dependence records if these data races are not dealt with. Unfortunately, if that
happens, an inconsistency may easily slip through into the domain.
A safe and easy way to deal with this problem is to leverage on the support for
concurrency given by the JVSTM. If we implement the association between the class

DependenceRecord and the class VBox transactionallythat is, using versioned sets
to keep the linksthen we may add and remove links to the association within a transaction without fear of those changes being made visible to other transactions or that
concurrent accesses to those links cause an inconsistency.
The idea is that the commit of a top-level write transaction checks the consistency of
its changes by executing all the needed consistency predicates before its validation phase.
That is, the consistency check is part of the normal transaction execution, except that
it is performed immediately before the commit, after all the user code has executed (so
that we know already which boxes have changed). During this consistency checking, the
transaction may read new boxes to evaluate the consistency predicates, but it may write,
also, to the boxes that are used to keep the dependence records and the vboxes connected
to each other. Naturally, all these new reads and writes are recorded in the transaction
read set and write set, respectively.
So, if some consistency predicate fails, the transaction simply aborts and all the
changes made to the dependence records are discarded, as usual. If, on the other hand, all
the consistency predicates succeed, then the commit of the top-level transaction proceeds
with the validation phase, where it may find that a dependence record was concurrently
updated by another transaction, in which case it detects a conflict and restarts the whole
transaction. Otherwise, if the transaction is valid, the commit of the transaction will
make the changes to the dependence records permanent.
Even though it is possible to find alternative solutions to this problem that avoid some
of the conflicts caused by changes in the dependencies, this implementation has the
advantage of simplicity, given that it relies on the existing support given by the JVSTM.
So, this was the solution that I adopted to implement the consistency predicates in the
JVSTM.

6.7 Related Work

6.7

Related Work

The idea that the state of an object must satisfy a set of predicates, often called invariants,
traces back to the work of Hoare [1972] on data-representation correctness. Later, Meyer
built on this idea, proposing the use of class invariants as one of the fundamental elements
of his design by contract methodology for object-oriented design [Meyer, 1988, 1992a].
The design by contract approach is a design methodology in which programmers must
specify a contract for each class. A contract is specified by a set of pre-conditions and
post-conditions for each of the public classs methods, as well as a set of class invariants.
Class invariants specified for a class C are predicates that must be true for all instances
of C that are publicly exposed to other parts of the program. They provide, thus, a set of
guarantees about the state of an instance of C.
As part of his work on the design by contract approach, Meyer built into the programming language Eiffel [Meyer, 1992b] a set of constructs to allow the specification of
contracts, including, therefore, the specification of class invariants. Having class invariants explicitly represented in a program allows the Eiffel runtime to check whether the
invariants are true during the execution of the program, thereby facilitating the detection
of errors.
Meanwhile, this design by contract approach at the programming language level has
been applied to many other languages, including, naturally, the Java programming language [Kramer, 1998; Karaorman, Hlzle, and Bruno, 1999; Bartetzko, 1999; Leavens,
Ruby, Rustan, Leino, Poll, and Jacobs, 2000; Flanagan, Leino, Lillibridge, Nelson, Saxe,
and Stata, 2002; Lackner, Krall, and Puntigam, 2002; Leavens, Cheon, Clifton, Ruby,
and Cok, 2005]. All of these extensions to Java follow, more or less closely, the semantics
of the constructs provided by Eiffel.
Even though consistency predicates resemble very much class invariants, there are
some fundamental differences between the two approaches.
First and foremost, there is a conceptual difference between the two approaches. One
of the basic tenets of all the design by contract approaches is that class invariants are
used in a program as a debugging tool only. That is, the idea is that class invariants
checking is performed during program execution only to detect programming errors that
may cause the invariants to be broken. In particular, a correct program should never
obtain a false result from the evaluation of class invariants. So, once a program has been
tested, class invariants may be disabled. In fact, Eiffel and most of the other approaches
that implement class invariants allow programs to run with all the checking disabled.
Consistency predicates, on the other hand, are not for debugging purposes, even
though they may be used like that. Rather, they implement a significant part of a domains
logic and, therefore, cannot be disabled, if the program is to work as expected. That is,

159

160

Consistency Predicates

the semantics of a program depends on the execution of consistency predicates. For this,
however, consistency predicates depend on the semantics of failure recovery given by
atomic actions, which are not generally available in the languages with support for class
invariants.
A second difference is on the expressiveness of the predicates. Class invariants as
proposed by Eiffel (and followed by all the design by contract extensions to Java) cannot
access arbitrary data nor call arbitrary methods to verify their conditions. Instead, they
must restrict themselves to access the private state of the object being verified. In particular, class invariants cannot access the state of an object referenced by the object being
checked, nor can they call a method on such an object. These restrictions impose severe limitations on the expressiveness of class invariants that preclude many interesting
invariants that depend on several objects.
In fact, several authors acknowledge this problem and propose to extend class invariants so that they may access the state of other objects [Barnett, DeLine, Fhndrich,
Rustan, Leino, and Schulte, 2004; Dietl and Mller, 2005; Mller, Poetzsch-Heffter, and
Leavens, 2006]. To accomplish that, they build on work on ownership models to limit and
to control the aliasing of objects to a set of well known places within an aggregate [Clarke,
Potter, and Noble, 1998]. Class invariants in such approaches are able to extend their
visibility to all the objects owned by the object being checked. Even though these approaches represent a step further, they are still too limited for the needs of a rich domain
model. In such a domain model, it is not practical to limit the aliasing between entity
types, given that they are often part of very intricate object graphs where there is no object
that is a natural owner of the others.
A third and final difference between the two approaches is on when the predicates
are evaluated. In Eiffel, class invariants are checked at the beginning and at the end
of the execution of each of the classs methods (except for helper methods), whereas
consistency predicates are checked only at the end of a transaction. I discussed already
in the beginning of this chapter, the differences between these two approaches.
Having atomic actions in a programming language is, actually, the key enabling element for having consistency predicates as I have proposed them in this dissertation:
Once we have atomic actions, the idea underlying consistency predicates is relatively
straightforward.
Therefore, it is no surprise that there are many things in common between the work
described in this chapter and the proposal made recently by Harris and Peyton-Jones
[2006] to add data invariants to transactional memory.
Even though Harris and Peyton-Jones are working in a different setting, with a different STM implementation made for the Haskell programming language, their implementation strategy is quite similar to mine: To reduce the number of predicates that need to

6.8 Summary

be evaluated at the end of a transaction, both implementations use a dependency record


between transactional locations and the predicates that depend on them. Also, they use
a nested transaction to evaluate each invariant (as they call them), but, unlike in my implementation, they allow the invariant to write to transactional locations and then abort
the nested transaction in the end to avoid side-effects.
Furthermore, to make the changes in the dependencies after all the invariants checking, they modify the commit algorithm of their STM implementation to deal specifically
with the dependencies that were changed, whereas I rely on the already existing mechanisms in the STM to accomplish that. In fact, my approach is sufficiently generic to
be applicable almost without changes to any other STM implementation that supports
nested transactions.
Another difference, now at the programming interface level, is that invariants are
dynamically specified during the execution of the program, rather than being a static
element of the program as consistency predicates are. Also unlike consistency predicates,
in their proposal invariants are checked whenever they are created, even though they are
checked again at the end of the transaction.
Finally, Harris and Peyton-Jones follow with the tradition of design by contract that
invariants are meant for debugging only. Yet, I argue that this conceptual difference has
a significant pragmatical influence on how consistency predicates (or invariants) are used
in the implementation of a rich domain model.

6.8

Summary

This chapter proposes to separate the implementation of domain constraints from the
implementation of the remaining aspects of a domain model. To allow this separation, it
proposes the use of consistency predicates, which are automatically executed when a toplevel write transaction commits to check whether the domain objects are in a consistent
state.
The chapter uses several of the domain constraints from the banking domain model to
compare their implementation using the current best-practices with the implementation
using consistency predicates. It describes, also, how consistency predicates may be used
to implement the enforcement of an associations multiplicities.
Finally, it describes the implementation of consistency predicates in the JVSTM and
discusses related work.

161

162

Consistency Predicates

Chapter 7

Validation
This dissertations thesis is that it is possible to simplify significantly the task of implementing an object-oriented domain model by making a small, non-disruptive, and easy to
implement set of additions to the current object-oriented programming languages. More
specifically, that we may achieve that goal by adding to an object-oriented programming
language support for atomic actions, a declarative language for specifying the structural
aspects of a domain model, and consistency predicates that validate atomic actions at
commit-time.
In the last three chapters, from Chapter 4 through Chapter 6, I made concrete proposals for each of these different additions. For each case, I identified the problems,
proposed a solution, and exemplified how that solution helps in solving the problems
identified. Moreover, I described how to implement each of the proposals, showing not
only that their implementation is feasible, but also that it is possible to do it with little
effort in a practical waythat is, without introducing major changes in the languages and
the tools used by software developers. In fact, all the work described thus far tries to
respect the guiding principles put forth in the introduction of this dissertation.
The purpose of this chapter is to complement the thesis validation that was given along
each proposal, by describing two applications of the proposals made in this dissertation.
First, I describe the application of all my proposals in the development of a large realworld web application: the Fnix system. The work described in this dissertation and its
application to the Fnix system are, actually, quite intertwined, as they have occurred in
parallel, influencing each other.
Second, I evaluate the performance of the JVSTM implementation for a more STM-like
traditional setting. Even though the JVSTM was developed with the goal of simplifying
the implementation of a domain-intensive application, in this chapter I report on how it
performs in two existing benchmarks for STMs.

164

Validation

7.1

The Fnix Case Study

The Fnix project is an open-source university management system that aims to incorporate all on-line campus activities and related management services [FenixEDU]. From
its initial deployment with an initial limited set of functionalities in 2001 until now (mid
2007) it has been continuously growing both in functionality and use.
In this section, I describe how the proposals made in this dissertation were employed
in the more recent development of this system.

7.1.1

Fnix History

The project has been in development at the Instituto Superior Tcnico (IST), from the
Technical University of Lisbon, since 2001.
The project started with a limited set of functionalities to support the creation and the
management of web pages for some of the ISTs courses, but has ever since expanded its
functionality to encompass most of the activities and management of the IST. Over time,
it replaced most of the schools legacy information systems that were becoming obsolete
and hard to maintain. Therefore, it went through an enormous pressure for increasing
its scope.
The Fnix system is now an indispensable element for all the ISTs activities and
users, which comprise over 12,000 students, 1,000 faculty, and 700 administrative staff.
In particular, most of the administrative staff depends on the system for their daily work.
Even though the IST continues to be the systems lead development organization, other
universities and companies joined the project and are now using it and further developing
it for deployment in other universities. Presently (mid 2007), the system is deployed in
three other universities and is being deployed in at least another three.
The initial development team at IST included only a handful of programmers but has
since expanded to include more programmers, as well as a Users Support and Requirements Team and a Web Design Team. The programmers team comprises a set of senior
members, all of them with a degree in software engineering or a related area, and a set
of graduation students that are, typically, in the last year of their degree in software
engineering at IST. The senior members work full-time on the project, whereas the graduation students work only half-time and during approximately one year as part of their
degree final project. I show in Table 7.1 the evolution in the composition of the Fnixs
programmers team over time.
I joined the Fnix team in 2004 as an external collaborator and became partly responsible for the systems architecture since 2005. Since that time, I have introduced

7.1 The Fnix Case Study

165

Number of members in the team by year


Type of Member
Senior
Graduation Students

2001/02

2002/03

2003/04

2004/05

2005/06

2006/07

5
2

5
12

7
19

7
16

14
10

12
2

Table 7.1: Composition of the Fnix projects programmers team at IST.

into the system the JVSTM, the DML, and the consistency predicates described in this
dissertation. Yet, my work was not to use them to implement the Fnix domain model.
Rather, the bulk of my work for the Fnix project has been in integrating my proposals
into the systems framework and architecture, so that the rest of the team may use them
to implement the domain model.

7.1.2

The Original Fnix Software Architecture

The core of the Fnix system is developed in the Java programming language and consists
of a web application that was initially developed by following the best-practices for that
software development area, as of 2001.
The system used the standard three-layered architecture of an enterprise application
that I discussed in Chapter 2 (see Figure 2.1 on page 16). Therefore, it separates the code
that implements the domain model from both the code that accesses the database and
the code that deals with the user interaction.
The implementation of the domain model, however, was separated in two distinct
elements: (1) the classes implementing the domain entities, which consisted mostly of
the entities properties, and getters and setters to access those properties; and (2) the
classes that implemented the applications services, which contained most of the domain
logic related to the behavior of the entities. This solution, in fact, is akin to the pattern
Transaction Script described by Fowler [2002].
Each service, implemented typically as a single method, used the services of the
data-source layer to access the objects representing the domain entities and operated on
those objects according to the domain models requirements. To control the concurrent
access to the domain entities that is inherent to this kind of applications, the Fnix
code resorted to the interface provided by the implementation of the ODMG 3.0 Object
Persistence API [Cattell, Barry, Berler, Eastman, Jordan, Russell, Schadow, Stanienda,
and Velez, 2000] available in the Object/Relational mapping tool that was used in the
project: the Apache DB Projects OJB [OJB].
The ODMG API includes operations for locking objects either for read or for write
before they are accessed. Unfortunately, this lock-based approach to concurrency was

166

Validation

highly error-prone, as programmers often forgot or misplaced the acquisition of locks,


causing frequent consistency problems into the domain data.
Moreover, with the increased usage of the system appeared also the first performance
problems, which were attributed, after some performance profiling, to the overheads incurred in the acquisition and the management of locks by the operations that accessed
many thousands of objects. In fact, these performance problems led to the development,
in 2004, of a preliminary version of the versioned STM described in this dissertation.

7.1.3

The Use of this Dissertations Work in the Development of the Fnix


System

Even though the first proposal that I made specifically for the Fnix system was the
versioned STM, the first of this dissertations proposals to be effectively employed in the
development of the system was the DML language proposed in Chapter 5. The Fnix
team started to use the DML language in April 2005 and the JVSTM was deployed later,
in September 2005. The consistency predicates, on the other hand, were deployed only
during 2007 with a limited implementation.
Thus, given that the experience of using consistency predicates in the Fnix system is
still very limited, in the following I shall describe only the use of the DML and the JVSTM
in the Fnix project.

7.1.3.1

Implementation of the Fnix Domain Model with the DML

The goal of introducing the DML language in the development of the Fnix domain model
was initially twofold. First, to eliminate the errors in the management of bidirectional
associations. Second, to simplify the introduction of the STM in the system.
The classes that implemented the domain entities in the Fnix system followed a common pattern: each class contained a set of attributes with a pair of getter and setter
methods for each of the attributes. Likewise, the implementation of associations consisted, in most cases, in one attribute in one (for unidirectional associations) or in both
(for bidirectional associations) of the participating classes, again with the corresponding
getter and setter methods; when the multiplicity of an associations end admitted more
than one element, the attribute used to implement that end of the association was of a
collection type.
This simple approach of implementing associations, however, had the problem that
the setters of each of the attributes that corresponded to an association did not enforce
the consistency of the association when the association was bidirectional. Thus, the
responsibility of ensuring the consistency of such associations was on the programmer

7.1 The Fnix Case Study

that wrote the code that needed to create or remove a link; the programmer would have
to invoke the two necessary methods to ensure that the link was consistently created or
removed. Unfortunately, this is too much error-prone, and this was one of the problems
that was causing significant troubles to the Fnix team and to the project.
On the other hand, to use an STM to control the concurrent access to the domain
entities, all the classes that implemented domain entities needed to be changed to become
transactional. Given that the number of domain classes was slightly over two hundred,
that was not an easy task to do. Moreover, back then, it was not clear whether the use
of an STM would be feasible for a system with the dimension of the Fnix system. So,
committing to that change was a risk too high for the project.
The use of the DML solved both of these problems. Having the domain models structure represented in a declarative way allows us (1) to generate the methods that implement
correctly the associations between the domain classes, and (2) to generate the domain
classes as transactional classes or as plain java classes, as we may see fitit is just a
matter of changing the DML compilers backend.
In fact, when the DML was first introduced in the development of the Fnix domain
model, in April 2005, the DML compiler generated the classes exactly as they were implemented in the system at that time. In particular, neither it generated the classes as
transactional, nor it generated the methods that implemented bidirectional associations
correctly.
The goal was to allow a gradual conversion from the Java classes to their DML substitutes, given that the large number of classes in the system would not allow a rapid
conversion. Indeed, this conversion process took place during the second quarter of
2005, from April to June, and was performed incrementally by all the team, while they
were developing new functionalities.
When finally all the classes and associations became represented in DML, we could
change the DML compilers backend to generate the code that ensured the consistency
of bidirectional associations; this occurred in July 2005. Later, with the introduction
of the JVSTM, in September 2005, the compiler was once again changed to generate
transactional classes. Given that the interface of the classes generated was always the
same, no further changes in the existing code were necessary in either case.
This use of the DML language illustrates one of its advantages that were already mentioned in Chapter 5that we may change the code generation strategy without changing
the remaining code of the system.

167

168

Validation

Lines of code for each date (1000)


Source Code Artifact

Apr 2005

Sep 2005

Jul 2007

64
121
112
28
18

37
5
92
107
115
33
21

136
12
195
89
238
8
1

Domain classes
DML code
(generated from the DML)
Remaining domain code (services)
Presentation layer
Persistence layer
OJB mappings

Table 7.2: Lines of code for different parts of the Fnix system.
7.1.3.2

Other Benefits of Using the DML

Even though the benefits described above are sufficient to justify the use of the DML for
implementing a domain models structure, there are other more far reaching benefits as
well.
Another advantage of the DML, of course, is that it reduces significantly the amount of
work required to implement a domain model. This is shown in Table 7.2, where I present
the approximate number of lines of code (LOC) of several parts of the system at three
different instants of its development: (1) in the beginning of April 2005, before starting
to use the DML language; (2) in the beginning of September 2005, when all the domain
classes were already in the DML and the JVSTM was introduced; and (3) as of this writing,
after almost two years of use of the DML and the JVSTM in the system.
From April 2005 to September 2005, the most significant change in the values presented in the table was in the implementation of the domain classes: From an initial
value of 64 thousand LOC for implementing the domain models classes, remained only
37 thousand LOC in the end. This value, however, does not reflect the actual reduction
in size of the code that implements the domain models structure for two reasons:
During that period the Fnix team continued to develop new functionalities and
to create new domain classes; the slight increase in the number of LOC in the
presentation layer indicates this, also.
In parallel with the conversion to a DML representation of the domain models
structure, the Fnix team started a refactoring process to move part of the code that
was in the classes implementing the services to the domain classes, instead. The
decrease of 15 thousand of LOC in the code of the services, despite the increase in
the systems functionality, confirm this.
If we consider both of this factors, it becomes clear that the real reduction of code
obtained with the DML should be much higher than what is shown in the table. In fact,

7.1 The Fnix Case Study

the code generated by the DML compiler in September 2005 for the 5 thousand lines of
DML code was about 92 thousand LOC of Java code. This value is much higher that the
initial size of the domain classes (64 thousand LOC) for several reasons: First, because the
code that is generated by the DML compiler implements correctly the bidirectionality of
the associations, where the original code did not; second, because all the associations are
now bidirectional; and third, because the DML compiler generates also the set of optional
methods that, in most cases, did not exist in the original domain model implementation.
Furthermore, one of the differences in the numbers from September 2005 to July
2007, reveal another benefit of using the DML that was realized only later: The almost
elimination of the OJB mappings.
The OJB mappings are a description in XML of how the domain classes and associations map into the tables of a relational database. As we have the information about the
domain classes and associations in a DML domain specification, those mappings may be
dynamically created from that domain specification, thereby eliminating the need to write
those mappings manually.
Finally, this possibility of using the information about the domain model structure is
being explored in several other places in the Fnix system as well:

To automate the generation of interfaces for presenting or accepting information


about a domain entity.
To generate documentation in the form of UML class diagrams to help in the visualization and the understanding of the domain model.

7.1.3.3

The JVSTM in the Fnix System

Unlike the introduction of the DML, which forced the refactoring of much of the domain
models code, the introduction of the JVSTM in the Fnix system was performed without
changes in the domain models code. The changes were all performed in the infrastructural code that supports the systems software architecture.1
To use the JVSTM in an application we need to do two things: (1) to implement the
mutable shared objects as transactional objects (by using the class VBox), and (2) that
we identify and annotate the atomic actions.
The first task is performed by the DML compiler, that generates the domain classes using versioned boxes to represent their attributes. The second task is easier to accomplish
in a web application, given that in most of the cases, they process each user request as
1

Actually, after the introduction of the JVSTM, the code that acquired the locks of the ODMG API was no
longer necessary, but removing that code was a simple matter of a global find and replace in the projects
source code.

169

170

Validation

Date
09/2005
12/2005
03/2006
06/2006
09/2006
12/2006
03/2007
06/2007

Classes

Associations

227
255
334
346
455
557
613
704

324
349
422
679
851
931
1011
1098

Table 7.3: Evolution of the number of classes and associations in the Fnix domain
model.

a single atomic action. So, we may wrap the entire request processing code with a transaction at the web-application-framework level, thereby removing from the programmers
hand the responsibility of doing it.
Even though I do not have data to quantify it, the perceived benefits for the Fnix
application that resulted from the combined use of the JVSTM with the DML, was an
increase in the applications performance, stability, and robustness.
The increase in performance results, on one hand, from the elimination of the overheads associated with lock acquisition and management, and, on the other hand, with
the improved utilization of the database cache, given that it is no longer necessary to pay
the penalty of a round-trip to database to ensure the consistency of the data.
The improved stability and robustness of the system results from the elimination of
inconsistencies caused by faulty lock acquisition and lack of bidirectional associations
management.
From the point of view of the software development task, the major benefit that results
from the use of the JVSTM is the simplification of the programming model. Not only
as a consequence of the simpler concurrency control approach, but also because some
architectural changes were made possible by the use of the JVSTM and the DML. For
instance, in Table 7.2 on page 168 we see a significant reduction in the LOC needed for
the persistence layer. This reduction results from an architectural change enabled both by
the JVSTM and the DML that eliminates the dependency that the domain layer had of the
persistence layer: For example, the code in the domain layer no longer needs to explicitly
store the objects in the database when they are changed; the JVSTM automatically does
that in the commit of a memory transaction, invoking the methods of the persistence layer
to persistently store the objects that were changed (provided that the memory transaction
is valid).
The simplification of the programming model allows for a faster evolution of the system,
given that it is simpler to develop new code and that programmers spend less time in

7.1 The Fnix Case Study

171

# associations

1098

# classes

227

0
Sep
2005

Dec
2005

Mar
2006

Jun
2006

Sep
2006

Dec
2006

Mar
2007

Jun
2007

Figure 7.1: Evolution of the number of classes and associations in the Fnix domain
model.

debugging their code. The evolution of the number of classes and associations in the
Fnix domain model, shown in Table 7.3 on the facing page and graphically depicted
in Figure 7.1, shows that the system has experienced a fast growth since the JVSTM and
the DML were introduced in the development of the system. In fact, the domain model
more than tripled its size in less than two years, even though the team has remained
more or less the same throughout that period.

7.1.4

The Fnix Transactional Workload

In Section 4.2, I presented the rationale underlying the development of the JVSTM. In
particular, I made a set of assumptions regarding the typical workload of a domainintensive application:

That the number of write transactions is low when compared with the total number
of transactions; probably, less than 10%.
That most of the transactions are medium-sized; that is, that the average size of
their read sets and write sets have hundreds to thousands of objects.
That occasionally there are long-running transactions that need to access thousands to millions of objects.
That an updating transaction may read many objects, but typically changes just a
few.

172

Validation

27878965

20909223

13939482

6969741

0
Oct
2006

Nov
2006

Dec
2006

Jan
2007

Feb
2007

Mar
2007

Apr
2007

May
2007

Jun
2007

Figure 7.2: Total transactions successfully processed by the Fnix web application
from October 2006 to June 2007.

To validate these assumptions, I instrumented the Fnix system to collect statistics


about the transactional system during its execution.
In its normal execution, the Fnix web application is deployed in a cluster with four
servers, which are Intel-based machines with 4Gb of memory and two 32-bit processors.
Each of these machines runs an instance of the Fnix web application within the Tomcat
application server.
From mid September 2006 until now, each application server writes periodically2 to
the database the values of a series of counters that each server maintains internally. The
values written by each server are the following: a server identification, a timestamp with
the current time, the number of read-only transactions processed, the number of write
transactions that successfully committed, and the number of conflicts.
Given these data, it is now possible to investigate what is the transactional workload
of the Fnix system. In the results shown here, I consider all the data collected between
the 1st of October 2006 and the 30th of June 2007, inclusive. This particular period has
the particularity that it spans an integral number of months (9), weeks (39), and days
(273).

7.1.4.1

Total Number of Transactions Over Time

In Figure 7.2, I show the monthly total of transactions successfully committed during the
period under examination. The decrease in the number of transactions during Novem2

The interval between each write is 5 minutes.

7.1 The Fnix Case Study

2665893

1999419

1332946

666473

0
1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728
Figure 7.3: Total daily transactions successfully processed by the Fnix web application on February 2007.

ber and December coincide with the end of the first semester, and with the Christmas
holidays.
On the other hand, the period from mid February to mid March corresponds to one of
the two periods of highest activity for the Fnix application that occur when all the ISTs
students (more than 12,000) enroll for all the courses and shifts for their next semester.
In fact, this year the enrollments for the second semester started on the 17th of February
2007, at 15:00, as can be seen in Figure 7.3 and in Figure 7.4.
Contrast the values shown for the enrollments day with the values shown in Figure 7.5 and in Figure 7.6, which present the daily and the hourly average of transactions
processed, respectively, over the period of nine months from October 2006 to June 2007.
Whereas on average the Fnix application processes less than one million transactions per
day (even though that value has been increasing over time), on the enrollments day the
total number of transactions exceeds two and a half millions. More dramatically, the total
number of transactions processed on the first hour after the start of the enrollments is
more than ten times the maximum number of transactions processed on average for each
hour of the day. These values show that the load of the system under normal conditions
is well below what it is able to process.
Furthermore, the values depicted in Figure 7.5 and in Figure 7.6 show that the workload of the system varies with the working patterns of the people using the system: There
are more transactions processed during the week than at weekends and the highest rate
of transactions per hour occur during the working hours, with a slight decrease around
the lunch and the dinner hours.

173

174

Validation

524157

393117

262078

131039

0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Figure 7.4: Total hourly transactions successfully processed by the Fnix web application on the first day of enrollments, the 17th of February 2007.

825131

618848

412565

206282

0
Mon

Tue

Wed

Thu

Fri

Sat

Sun

Figure 7.5: Average daily number of transactions successfully processed by the Fnix
web application for each day of the week from October 2006 to June 2007.

7.1 The Fnix Case Study

50168

37626

25084

12542

0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Figure 7.6: Average hourly number of transactions successfully processed by the
Fnix web application from October 2006 to June 2007.

7.1.4.2

The Read/Write and Write/Conflicts Ratios

The numbers shown so far show the total number of transactions successfully processed
by the system, without distinguishing between read-only and write transactions.
Yet, one of the assumptions that influenced significantly the design of the JVSTM
was that read-only transactions largely outnumber write transactions. In the Fnix web
application, that proves to be the case, as we may see in Figure 7.7. Given that this
plot is in a logarithmic scale, the monthly totals show that the number of read-only
transactions is two orders of magnitude higher than the number of write transactions.
Likewise, the number of successful write transactions is two orders of magnitude higher
than the number of conflicts.
Looking at these values monthly, however, may hide differences in these ratios, given
that the write transactions (and, therefore, the higher probability of conflicts) are more
concentrated during the working hours of the administrative staff. Thus, in Figure 7.8, I
show the hourly average for each of the three values. This plot confirms that even though
the number of write transactions and conflicts increases during the working hours, they
maintain the ratios shown before.
Finally, the stress test for these ratios is the enrollments day, where thousands of
students are rushing to get the best timetables and, therefore, contending for the same
domain objects. The hourly totals for the enrollments day are depicted in Figure 7.9,
which show a significant reduction in both ratios on the first hour after the start of the
enrollments period. Nevertheless, even in that pathological case, there is still an order of
magnitude difference between each value.

175

176

Validation

reads

100000000
10000000
1000000

writes

100000
conflicts
10000
1000
100
10
1
Oct
2006

Nov
2006

Dec
2006

Jan
2007

Feb
2007

Mar
2007

Apr
2007

May
2007

Jun
2007

Figure 7.7: Monthly total of read transactions, write transactions, and conflicts in
the Fnix web application from October 2006 to June 2007. The yy axis is in a
logarithmic scale.

100000

reads

10000
1000
writes
100
10
conflicts
1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Figure 7.8: Hourly average of read transactions, write transactions, and conflicts
in the Fnix web application from October 2006 to June 2007. The yy axis is in a
logarithmic scale.

7.1 The Fnix Case Study

177

1000000
100000

reads

10000
writes

1000
100
10

conflicts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Figure 7.9: Hourly total of read transactions, write transactions, and conflicts in the
Fnix web application on the first day of enrollments, the 17th of February 2007. The
yy axis is in a logarithmic scale.

7.1.4.3

The Dimension of the Transactions

The previous results confirm that the Fnix web application exhibits the transactional
workload, in terms of read/write ratio, for which the JVSTM was designed. Namely, that
there are many more reads that writes.
The remaining assumptions underlying the design of the JVSTM are related to the
dimension of the transactionsthat is, how many transactional reads and writes are
performed by each transaction.
To assess the dimension of the Fnix transactions, I instrumented the JVSTM used
in the Fnix servers to count the number of boxes read and written during each transaction. These values are then accumulated and written to the database along the previously
written statistics. The new statistical values collected for each period of 5 minutes are
the accumulated number of boxes read (or written) by all the transactions, and the maximum number of boxes read (or written) by a single transaction. Moreover, these values
are collected for each of the two types of transaction: read-only transactions and write
transactions.
The values that I report here were collected by the Fnix servers for a continuous
period of almost 14 days in July 2007. The total number of statistical records collected
during that period was 18847, each one corresponding to a 5 minutes period of one server.
In Table 7.4, I show both the average and the maximum number of boxes read or
written for each type of transaction during this period. The number of boxes written
on average by each transaction is, as expected, very low, even though there are some

178

Validation

Number of boxes accessed


Operation/type of transaction

Average

Maximum

Reads/read-only transaction
Reads/write transaction
Writes/write transaction

5,844
47,226
35

63,746,562
2,292,625
32,340

Table 7.4: Number of boxes accessed by each type of transaction by the Fnix web
application.
Number of periods by type of transaction
Maximum number of boxes read
Max
Max
Max
Max

>
>
>
>

10,000,000 boxes
1,000,000 boxes
500,000 boxes
100,000 boxes

Read-Only transactions

Write transactions

15
461
1,121
9,763

0
36
60
8,377

Table 7.5: Number of large transactions, for each type of transaction, in the Fnix
web application. The count in each row includes the value of the row above.

transactions that write a more significant amount of boxes. Nevertheless, compared to


both the average and the maximum number of boxes read by a write transaction, the
number of boxes written is much lower. This suggests that it is a good design decision
to concentrate on reducing the overheads associated with the read of a transactional
location for this type of applications.
More so, if we look into the maximum number of boxes read by read-only transactions.
During the period under analysis, the largest read-only transaction performed more than
63 millions of reads. Such a large number, however, may have been a sporadic transaction
not occurring often. So, to see how frequent large transactions are in the Fnix web
application, I counted how many records exist with a maximum number of reads larger
than a certain threshold. The results of this counting are shown in Table 7.5. Considering
that this is a small period of less than 14 days, we may see that, on average, the Fnix
application processes, per day, at least one transaction that performs more than 10
millions of reads. Lowering the number of boxes read to one million, we have more than
32 such transactions per day, on average. Finally, on average, each 10 minutes we have
at least one transaction that reads more than 100 thousand boxes.
Note, however, that these numbers are lower bounds for the number of transactions
reading such number of boxes, given that we may have many transactions in a 5 minute
period.
Also, I would like to draw your attention to the comparison between the numbers for
read-only and for write transactions. Even though for higher counts of boxes the number
of write transactions is significantly lower than the number of read-only transactions, we

7.2 JVSTM Performance

see, surprisingly, that there is almost a similar number of write transactions reading more
than 100 thousand boxes. On one hand, this result is surprising, because the overall
number of read-only transactions is much higher than the number of write transactions.
On the other hand, it is in accordance with the fact that on average, write transactions
read almost 50 thousand boxes, ten times the average of read-only transactions.
Finally, these high number of reads performed by some read-only transactions, together with the fact that most of the transactions processed by the system are read-only
transactions, justify the use of speculative read-only transactions in the Fnix application. In fact, the use of speculative transactions in the Fnix application, eliminated
some of the out-of-memory errors that were caused by the read sets of such very large
transactions. Unfortunately, for write transactions, no similar optimization exists.

7.2

JVSTM Performance

The literature on Software Transactional Memory uses, typically, a set of simple benchmarks to evaluate and to compare different STM implementations. In most cases, those
benchmarks consist of a couple of operations operating on a data structure such as a redblack tree, or a linked list. The usual measure of performance for such benchmarks is
the number of transactions per second that each STM implementation delivers when running the benchmark for a fixed amount of time. One such set of benchmarks is publicly
available with the implementation of the DSTM2 proposed by Herlihy et al. [2006].
More recently, Guerraoui, Kapalka, and Vitek [2007] proposed the STMBench7 benchmark as a more realistic example of what should an STM implementation be prepared to
find in a real-world application. Even though the STMBench7 is still very limited in size
when compared with the Fnix system, it is, however, much more realistic than the small
examples typically used in this area.
Therefore, in this section I use these two benchmarks to evaluate the performance of
the JVSTM. Even though the goal for the JVSTM was not to provide the best-performing
STM implementation, it is a stated goal of this dissertation that the implementation of the
STM proposed be suitable for practical use. This brief evaluation shall confirm that this
goal is attained.

7.2.1

Benchmark Running Environment

All the results presented in this section were obtained in a single machine with two
dual-core AMD Opteron processors, which gives us the total of four available cores. The
machine runs Linux and all the tests were performed without any other significant process
running in the machine.

179

180

Validation

All the tests were compiled and executed with version 1.5.0_11-b03 of the Suns Java
Runtime Environment using the default options.

7.2.2

Results for the DSTM2 Benchmarks

The publicly available implementation of the DSTM2 [DSTM2] includes three variations of
a benchmark that consists in the repeated insertion, removal, and search of a randomly
chosen integer in a set of integers. The set of integers is implemented either as a sorted
single linked list, a skip list, or a red-black tree.
The benchmark allows the specification of the number of threads to spawn, the percentage of updates that each thread should perform, how long should the benchmark run,
and which STM implementation should be used. The choice of the STM implementation
is made though the specification of a transactional factory that implements the interface
specified by the DSTM2 framework [Herlihy et al., 2006].
I created two factories to run these tests: one using the JVSTM, and another that uses
a single global lock to ensure exclusive access to the data structure in each operation.
Moreover, I made some minor changes to the benchmark to ensure that the sets have
a bounded size. More specifically, I limited the integers that are randomly chosen by the
benchmark to be within the range 0 to 1023. Thus, each set will have a maximum size of
1024 elements. Also, all the benchmarks start with a set that was previously initialized to
contain half of its maximum size (512 elements in this case) of randomly chosen integers.
Given that the argument of each of the three operations performed on the set is randomly
chosen with a uniform distribution, and that the inserts and the removals alternate with
each other, the set will have, on average, 512 elements. So, the probability of a search
operation returning true is 0.5. Likewise, both the insertion and the removal of an integer
have a probability of 0.5 of not changing the set. One consequence of this randomness is
that the effective number of write transactions for a requested workload of n % of updates
is in reality only half of that.
I ran each of the three benchmarks varying the following parameters:

The number of threads to use (one of: 1, 2, 4, 8, 16, or 32).


The percentage of updates (one of: 100%, 50%, 10%, or 0%).
The transactional factory to use, which was one of the following:
ofree: This factory is provided with the DSTM2 and is one of the factories described in the paper that introduces the DSTM2. It implements the obstructionfree DSTM described previously in [Herlihy et al., 2003], but using a visible
reads approach.

7.2 JVSTM Performance

Shadow: This is another factory provided with the DSTM2 code and described
in the paper.
JVSTM: This is the factory that uses the JVSTM, but where all the transactions
start as possible read-write transactions.
Speculative: This is a variation of the previous factory, which starts all transactions speculatively as read-only transactions and restarts them when a write
is attempted.
JVSTM-ROH: This is a variation of the JVSTM factory that follows the eventual read-only hints given when a transaction starts. The code of the DSTM2
benchmark was changed to allow the specification, by the programmer of an
atomic action, that a given transaction is read-only. In particular, all the executions of the search operation are made as read-only. This factory follows
these hints by starting the transactions marked as read-only in that mode.
Lock: This factory uses a single global exclusive lock to prevent that two concurrent threads access the same data structure.
The two STM implementations that came with the DSTM2 need a contention manager
to resolve the conflicts. Even though there are many contention management policies
proposed in the literature,3 in these tests I did not explore variations of contention management. Instead, in both cases, I used the default value configured in the DSTM2 code:
the backoff contention manager.
For each combination of the parameters, I ran 8 different executions of the benchmark
for 20 seconds. The value of transactions per second that I use in the following discussion
of the results is the average of the 8 values obtained with these runs.
In the following, I shall present the results for each of the 3 benchmarks. I present
in tables the results obtained for all the six factories. To facilitate the comparison of the
various STMs, I present the results graphically also in form of plots in a logarithmic scale.
In these plots, however, I do not show the results for all the variants of the JVSTM, to
avoid cluttering the plot. Instead, in the plots I present only the results for the Speculative
factory.

7.2.2.1

Results for the List Benchmark

I show, in Table 7.6 through Table 7.9 the results for the List Benchmark. The plots corresponding to these values are depicted in Figure 7.10 through Figure 7.13, respectively.
In this benchmark, the JVSTM performs much better than either of the two DSTM2
implementations, but it is at least an order of magnitude worse than the simple lock
approach for write dominated loads.
3

See, for instance [Guerraoui, Herlihy, and Pochon, 2005; Scherer III and Scott, 2004, 2005].

181

182

Validation

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

422938
23916
23080
18074
640
552

214365
28946
31496
26164
523
379

374421
33203
34473
39042
557
372

375223
13074
14344
12218
494
342

359732
11162
12563
10061
603
390

365472
6197
5677
4451
585
380

Table 7.6: The results for the List Benchmark with 100% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

414173
25577
30229
25620
834
662

207505
40761
46798
42070
712
483

337147
51786
66985
73418
741
436

351625
29286
22859
27900
765
490

336868
16180
16433
18147
875
601

342779
11146
9724
9438
930
691

Table 7.7: The results for the List Benchmark with 50% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

433732
28364
39469
37935
1311
855

249314
48178
73042
73024
1362
1000

373933
72879
145393
142326
1342
1071

353379
36034
51281
47071
1402
1187

359725
38481
47992
43897
1591
1423

334260
19024
33455
30018
1706
1617

Table 7.8: The results for the List Benchmark with 10% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

800430
34535
57948
57291
2649
991

374063
53970
114281
115751
3728
1910

702136
79273
236709
225987
5564
2998

705199
80951
223788
227925
5617
3020

714766
81278
219088
235643
5749
2864

688335
81497
230003
226171
5512
2743

Table 7.9: The results for the List Benchmark with 0% of updates.

7.2 JVSTM Performance

183

List Benchmark (100% updates)

1000000

Transactions/second

100000
Lock
10000
1000

Speculative
Shadow

100

ofree

10
1
1

16

32

Number of threads

Figure 7.10: Transactions per second processed by each method for the List Benchmark with 100% of updates.

List Benchmark (50% updates)

1000000

Transactions/second

100000
Lock
10000
Speculative

1000

Shadow

100

ofree
10
1
1

16

32

Number of threads

Figure 7.11: Transactions per second processed by each method for the List Benchmark with 50% of updates.

184

Validation

List Benchmark (10% updates)

1000000

Transactions/second

100000
Lock
10000
Speculative
1000
Shadow
100

ofree

10
1
1

16

32

Number of threads

Figure 7.12: Transactions per second processed by each method for the List Benchmark with 10% of updates.

List Benchmark (0% updates)

1000000

Transactions/second

100000

Lock
Speculative

10000
1000

Shadow
ofree

100
10
1
1

16

32

Number of threads

Figure 7.13: Transactions per second processed by each method for the List Benchmark with 0% of updates.

7.2 JVSTM Performance

185

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

1283214
132044
131348
58185
1186
1102

553313
160152
162268
98303
1375
211

1164354
88145
90303
111385
1367
17

1210855
92856
94012
73634
1384
14

1206412
87676
87099
64784
1369
13

1166084
83972
82766
63891
1364
45

Table 7.10: The results for the Red-Black Tree Benchmark with 100% of updates.

The exclusive lock approach, as expected, performs better with a single thread than
with more threads. The JVSTM, on the other hand, scales well, regardless of the workload
used, up to 4 threads. After that number, the performance degrades rapidly, except for
the case of 0% of updates. This sudden fall of the performance when the number of
threads is higher than the number of processors results from an increasing number of
conflicts, and, therefore, restarts, of the transactions. Given that all the transactions
must traverse the list nodes from the beginning up to the point of insertion or removal,
they have a high probability of traversing a node that is concurrently updated by another
thread. This effect is more visible when the rate of updates is highest.
The results for the DSTM2 implementations place the Shadow factory above the ofree,
which is consistent with the results presented in [Herlihy et al., 2006].
These results show, also, the overheads of generic transactions when compared to
read-only transactions. Even though the JVSTM with the speculative transactions has to
restart all the transactions erroneously assumed to be read-only, that is largely compensated by the gains obtained in all the tests, except when we have only 1 thread with 100%
updates.
Finally, an interesting result is that, in many cases, the JVSTM with speculative
transactions performs even better than the JVSTM-ROH because, in reality, only half of
the update operations are updating transactions. So, whereas the JVSTM-ROH uses a
generic read-write transaction for all the update operations, the JVSTM with speculative
transactions tries always a read-only transaction first, which proves to be true in at least
half of the cases (for a workload of 100%).

7.2.2.2

Results for the Red-Black Tree Benchmark

I show, in Table 7.10 through Table 7.13 the results for the Red-Black Tree Benchmark.
These values are depicted graphically, also, in Figure 7.14 through Figure 7.17.
Like in the previous case, the JVSTM performs much better than both of the DSTM2
implementations, but is much worse than the simple lock approach. In this case, however,

186

Validation

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

1440942
182191
200083
98344
2146
1986

685616
255390
278694
170429
2321
326

1323005
181625
182988
229593
2363
42

1320038
163095
163429
135732
2468
37

1322938
147826
151694
126116
2470
55

1323574
140170
148615
113520
2377
155

Table 7.11: The results for the Red-Black Tree Benchmark with 50% of updates.
Transactions/second by number of threads
Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

1596168
304063
405617
283056
7619
6450

898924
451420
616440
469520
3005
1147

1360411
607360
898527
751179
3391
77

1465382
403839
519580
440848
5719
184

1456302
366217
465813
400654
5451
335

1470091
354499
450957
383148
5886
947

Table 7.12: The results for the Red-Black Tree Benchmark with 10% of updates.
Transactions/second by number of threads
Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

1752592
400369
601922
597068
70686
29887

1180457
626557
1004470
1024964
86704
48232

1557793
860508
1576576
1563086
105488
68262

1680455
842556
1605674
1530231
83349
65963

1661551
886790
1614081
1600315
86818
62677

1627463
908179
1608275
1595123
85619
65050

Table 7.13: The results for the Red-Black Tree Benchmark with 0% of updates.
Red-Black Tree Benchmark (100% updates)

1000000

Lock

Transactions/second

100000
10000

Speculative

1000
Shadow
100
10
ofree
1
1

16

32

Number of threads

Figure 7.14: Transactions per second processed by each method for the Red-Black
Tree Benchmark with 100% of updates.

7.2 JVSTM Performance

187

Red-Black Tree Benchmark (50% updates)

1000000

Lock

Transactions/second

100000

Speculative

10000
1000

Shadow
100
10

ofree

1
1

16

32

Number of threads

Figure 7.15: Transactions per second processed by each method for the Red-Black
Tree Benchmark with 50% of updates.

Red-Black Tree Benchmark (10% updates)

1000000

Lock

Transactions/second

100000

Speculative
10000
Shadow

1000
100

ofree

10
1
1

16

32

Number of threads

Figure 7.16: Transactions per second processed by each method for the Red-Black
Tree Benchmark with 10% of updates.

188

Validation

Red-Black Tree Benchmark (0% updates)

1000000

Lock
Speculative

Transactions/second

100000

Shadow

10000

ofree
1000
100
10
1
1

16

32

Number of threads

Figure 7.17: Transactions per second processed by each method for the Red-Black
Tree Benchmark with 0% of updates.

there is a large difference between different workloads. For write-dominated workloads


the JVSTM starts to degrade its performance with 4 threads and the overall throughput
is much lower than for read-dominated workloads.
The problem with this benchmark is that most of the changes in the set cause a rebalancing of the tree that may propagate often to the root of the tree, thereby creating a
contention point in the data structure. This single point of contention is, also, the reason
for the disastrous performance of the ofree: As this implementation uses visible reads
and all the transactions read the root of the tree, a transaction that is trying to change
that node either backs off or it aborts the readers; in either case, no progress is made.
This particular problem does not affect the JVSTM, given that reads do not perturb
nor are perturbed by the remaining of the system. In fact, the results obtained by the
JVSTM for this benchmark when we have only 10% of updates are the best among all the
three benchmarks. So, for read-dominated workloads, a red-black tree may be a good fit
for the JVSTM.

7.2.2.3

Results for the Skip List Benchmark

Finally, I show, in Table 7.14 through Table 7.17 the results for the Skip List Benchmark.
These values are depicted graphically, also, in Figure 7.18 through Figure 7.21.
This is the first benchmark where the JVSTM performs better than the lock-based
approach. In particular, for all the workloads except the one with 100% of updates, the
JVSTM has a higher transaction rate with 4 threads than the lock-based approach.

7.2 JVSTM Performance

189

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

305121
68128
68905
47857
1965
1768

158993
102747
102593
80641
396
520

163357
103485
103825
128983
68
61

170373
75967
72128
68326
49
36

166545
64742
66043
55606
48
37

198497
59258
62111
53011
46
37

Table 7.14: The results for the Skip List Benchmark with 100% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

318502
77654
92628
68632
3269
2896

172220
128398
158112
129393
692
1017

175241
180405
212225
215560
156
102

182282
108499
123948
114377
94
83

212797
94706
103584
94131
77
64

225285
89580
97521
85970
76
106

Table 7.15: The results for the Skip List Benchmark with 50% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

359652
99094
144467
132659
9876
6975

214196
160091
271900
236497
3079
3421

202393
235781
466776
440954
635
388

215050
158337
247383
230325
347
298

229224
139398
227163
211212
384
456

255033
133906
220600
203021
500
553

Table 7.16: The results for the Skip List Benchmark with 10% of updates.

Transactions/second by number of threads


Factory
Lock
JVSTM
JVSTM-ROH
Speculative
Shadow
ofree

16

32

444574
138673
239763
224471
48369
18092

240094
227342
421094
438824
39520
25335

277495
307955
805865
782900
45951
35360

276243
321226
825336
773491
34333
32569

296080
319811
822072
806197
35713
31752

312701
328089
842003
798779
32099
31589

Table 7.17: The results for the Skip List Benchmark with 0% of updates.

190

Validation

Skip List Benchmark (100% updates)

1000000

Transactions/second

100000
Lock

10000

Speculative

1000
100
10

Shadow
ofree

1
1

16

32

Number of threads

Figure 7.18: Transactions per second processed by each method for the Skip List
Benchmark with 100% of updates.

Skip List Benchmark (50% updates)

1000000

Transactions/second

100000
Lock
Speculative

10000
1000
100

Shadow
ofree

10
1
1

16

32

Number of threads

Figure 7.19: Transactions per second processed by each method for the Skip List
Benchmark with 50% of updates.

7.2 JVSTM Performance

191

Skip List Benchmark (10% updates)

1000000
100000
Transactions/second

Lock
Speculative

10000
1000
100

Shadow
ofree

10
1
1

16

32

Number of threads

Figure 7.20: Transactions per second processed by each method for the Skip List
Benchmark with 10% of updates.

Skip List Benchmark (0% updates)

1000000

Transactions/second

100000

Speculative
Lock

10000

Shadow
ofree

1000
100
10
1
1

16

32

Number of threads

Figure 7.21: Transactions per second processed by each method for the Skip List
Benchmark with 0% of updates.

192

Validation

7.2.3

Results for the STMBench7 Benchmark

The STMBench7 benchmark was proposed recently by Guerraoui et al. [2007] as a more
realistic benchmark for evaluating STM implementations. This benchmark is an adaptation of the OO7 benchmark [Carey, DeWitt, and Naughton, 1993]: It maintained the
underlying data structure of the original benchmark, but removed the database-related
parts and added a set of new operations that allow for a more adequate evaluation of
STMs.

7.2.3.1

The STMBench7 Data Structure

The data structure of the STMBench7 benchmark consists in a large number of objects of
several different typesmodules, assemblies, composite parts, atomic parts, connections,
documents, and manualswhich are organized as follows: There is a single module
that contains a seven-level-deep tree of assemblies, where each level of the tree has tree
children; each of the leaves of this tree contains several composite parts, which, in turn,
have a document and a graph of atomic parts which are connected via connection objects.
All these objects, which are called generically design-library objects, are related with one
another either by using simple references between them, or by using collections of objects.
The benchmark starts with the creation of this data structure, using a pair of factories:
(1) a factory for creating each of the design-library objects, and (2) a factory for creating the
sets, bags, and indexes needed to relate the objects with one another. The factories are
used by the STMBench7 benchmark to allow that different STM implementations provide
their own version of the elements that compose the data structure.
Therefore, I created one implementation of each of these factories using the JVSTM.
For implementing the design-library objects, I used a vbox for each of their fields and
maintained all the remaining code, except that where a field access was made, there is
now a vbox operation. For implementing the collections, I used a simple approach: I
implemented two purely functional data structures, which are, thus, thread-safe, and
used a single vbox to hold an instance of the collection. So, any operation that changes
a collection creates a new instance of the collection and changes the vbox to that new
value.
The two purely functional data structures that I used to implement the collections of
the STMBench7 benchmark were: (1) a single linked list, and (2) a red-black tree that
follows the implementation described in [Okasaki, 1998].

7.2 JVSTM Performance

7.2.3.2

The STMBench7 Operations

To operate on its data structure, the STMBench7 benchmark implements 45 distinct


operations, which are organized across two different dimensions: the operation category,
and whether the operations are read-only or read-write.
The four categories for the operations are:
Long traversals. These operations traverse most of the objects in the data structure,
starting either at the module and going top-down, or starting at an atomic part and
going bottom-up. These are the lengthier operations in the benchmark.
Short traversals. These operations are similar to the previous ones, except that they
traverse a smaller number of objects.
Short operations. These operations access only a few objects and perform some
operation on those objects.
Structure modification operations. These operations make changes in the data structure, by creating or deleting elements. In this category there are no read-only operations.

7.2.3.3

The STMBench7 Execution Parameters and Results

The STMBench7 benchmark allows us to specify, for each run, the number of threads
to use, the type of workload pretended, which synchronization strategy to use, and for
how long should each thread run; additionally, it allows us to disable certain categories
of operations.
There are three predefined workload values in the STMBench7, corresponding to different splits between read-only and read-write operations: read-dominated (90% reads/10%
writes), read-write (60% reads/40% writes), and write-dominated (10% reads/90% writes).
The STMBench7 assigns a probability for each of its operations by taking into account
the workload type chosen and a predefined ratio assigned to each category type. This
probability is then used by each thread to choose randomly the sequence of operations to
execute.
Regarding the choice of synchronization strategy, the STMBench7 benchmark comes
already with two locking strategies implemented:
A coarse-grained locking strategy that uses a single read-write lock for the entire
data structure.
A medium-grained locking strategy that uses one read-write lock for each level of
the data structure.

193

194

Validation

The intended semantics for the synchronization strategies is that each operation executes atomically. Thus, to test the JVSTM, I implemented another synchronization strategy that wraps each operation with a JVSTM transaction. Moreover, as in the STMBench7
benchmark we know whether each operation is read-only or not, the JVSTM strategy uses
that information to create a transaction of the appropriate type.
As for the operations to use, the original version of the STMBench7 allows us to disable, independently, all the long traversals, and all the structure modification operations.
I extended it to allow us to disable, also, the read-write long traversals (but leaving the
read-only long traversals active).
Finally, there are two types of results produced at the end of a run: (1) the total
throughput of the benchmark, measured in number of operations per second; and (2) the
maximum latency for each type of operation. According to the authors of the benchmark,
there are two typical uses for it: either we run the benchmark with all the operations
enabled to measure the latency of the operations, or we run the benchmark with no long
traversals active to measure the throughput.

7.2.3.4

Experimental Setup

I ran a series of tests varying the synchronization strategy, the number of threads, the
workload type, and the mix of operations.
Given that the latency results of the benchmark may be significantly influenced by
the random execution of the JIT compiler of the Java virtual machine, I changed the
benchmark so that it runs for 60 seconds in the beginning to warm up the virtual machine;
after that initial warm up, the benchmark runs for the specified amount of time and,
obviously, the results are measured only for that part of the run. The results shown
below were obtained by running each test for 60 seconds.
I show results for each of the three workload types and synchronization strategies,
varying the number of threads through the values 1, 2, 4, 8, and 16.
The throughput results were obtained for two mixes of operations: one with all the
long traversals disabled, and another with only the read-write long traversals disabled.
The latency results were obtained with all the operations enabled.

7.2.3.5

Throughput Results

I show the results obtained for the throughput tests with all the long traversals disabled
in Table 7.18 through Table 7.20, where each table is for a particular workload type. Also,
I show these values graphically in Figure 7.22 through Figure 7.24.

7.2 JVSTM Performance

195

Operations/second by number of threads


Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

2362
2447
1632

2948
3269
2585

3302
4189
4260

2838
4080
2297

2673
3988
1835

2637
3876
1495

Table 7.18: The results of the STMBench7 benchmark with all the long traversals
disabled and a read-dominated workload.
Operations/second by number of threads
Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

1569
1528
947

1418
1698
1362

1498
1887
1453

1415
1769
1007

1459
1846
667

1489
1692
441

Table 7.19: The results of the STMBench7 benchmark with all the long traversals
disabled and a read-write workload.
Operations/second by number of threads
Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

936
908
578

925
945
652

875
972
620

863
970
427

821
909
316

844
901
234

Table 7.20: The results of the STMBench7 benchmark with all the long traversals
disabled and a write-dominated workload.
No long traversals / Read-dominated

4260

Operations/second

medium
3195
coarse
2130
JVSTM
1065

0
1

16

32

Number of threads

Figure 7.22: Operations per second processed by each synchronization strategy


for the STMBench7 benchmark with all the long traversals disabled and a readdominated workload.

196

Validation

No long traversals / Read-write

1887

Operations/second

medium
coarse

1415

944

472

JVSTM

0
1

16

32

Number of threads

Figure 7.23: Operations per second processed by each synchronization strategy for
the STMBench7 benchmark with all the long traversals disabled and a read-write
workload.

No long traversals / Write-dominated

972

Operations/second

medium
coarse
729

486

243

JVSTM

0
1

16

32

Number of threads

Figure 7.24: Operations per second processed by each synchronization strategy


for the STMBench7 benchmark with all the long traversals disabled and a writedominated workload.

7.2 JVSTM Performance

197

Operations/second by number of threads


Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

131
135
101

163
172
198

139
193
265

131
185
305

140
191
306

172
237
359

Table 7.21: The results of the STMBench7 benchmark with all the read-write long
traversals disabled and a read-dominated workload.

Operations/second by number of threads


Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

398
427
130

275
320
253

308
631
991

372
558
514

586
524
413

430
385
359

Table 7.22: The results of the STMBench7 benchmark with all the read-write long
traversals disabled and a read-write workload.

These results show that, even though the JVSTM performs worse than either of the
locking strategies with one thread, it scales much better than the locking strategies. In
fact, for a read-dominated workload and four threads the JVSTM is clearly better than the
coarse-grained locking and slightly better than the medium-grained locking. Also, from
the shape of the plot, it is reasonable to expect that this difference would become more
significant with more processors.
Unfortunately, the performance of the JVSTM decreases abruptly when we have more
threads than processors. This result is caused in part by the fact that the STMBench7
is not concurrency friendlythat is, there is lots of contention in the benchmark. Thus,
as the number of threads (and consequently the number of transactions) increases, the
probability of a conflict increases dramatically. Not only because it increases the probability that a concurrent write transaction exists, but also because the duration of each
transaction increases.
Nevertheless, these results are very encouraging for the JVSTM. Specially, when compared with the results reported by the authors of the STMBench7 benchmark in their
paper for an implementation of another STMthe ASTM. According to those results, the
ASTM cannot achieve the 10 operations per second in this benchmark, whereas the results presented for the locking strategy are very similar to those obtained by me. So, it is
reasonable to say that the JVSTM performs at least one to two orders of magnitude better
than the implementation used by the authors of the STMBench7.
In fact, whereas Guerraoui et al. report that the ASTM takes roughly half an hour to
execute one of the long traversals (namely, T1), when running with only one thread, the
JVSTM executes that operation in less than 1.4 seconds in my experiments.

198

Validation

Operations/second by number of threads


Synchronization strategy
Coarse-grained locking
Medium-grained locking
JVSTM

16

32

822
803
470

844
949
543

873
923
603

859
893
427

845
837
315

839
863
229

Table 7.23: The results of the STMBench7 benchmark with all the read-write long
traversals disabled and a write-dominated workload.

No read-write long traversals / Read-dominated

JVSTM

Operations/second

359

269
medium
180

coarse

90

0
1

16

32

Number of threads

Figure 7.25: Operations per second processed by each synchronization strategy for
the STMBench7 benchmark with all the read-write long traversals disabled and a
read-dominated workload.

7.2 JVSTM Performance

199

No read-write long traversals / Read-write

Operations/second

991

743

495
coarse
medium
JVSTM
248

0
1

16

32

Number of threads

Figure 7.26: Operations per second processed by each synchronization strategy for
the STMBench7 benchmark with all the read-write long traversals disabled and a
read-write workload.

No read-write long traversals / Write-dominated

949

Operations/second

medium
coarse
712

475

237

JVSTM

0
1

16

32

Number of threads

Figure 7.27: Operations per second processed by each synchronization strategy for
the STMBench7 benchmark with all the read-write long traversals disabled and a
write-dominated workload.

200

Validation

Max latency by number of threads (ms)


Synchronization strategy

Read-only oper.

Coarse-grained locking

Short
Short
Short
Short
Short
Short

Medium-grained locking
JVSTM

traversals
operations
traversals
operations
traversals
operations

16

11
3
9
3
2
4

3564
4011
5891
5720
2
5

1702
2427
2500
4011
7
3

4256
4383
4256
4256
222
303

4515
4515
10960
10330
393
6068

Table 7.24: Maximum latency results for read-only short traversals and short operations with all the operations enabled and a read-dominated workload.

Max latency by number of threads (ms)


Synchronization strategy

Read-only oper.

16

Coarse-grained locking

Short
Short
Short
Short
Short
Short

1
3
8
3
4
7

4650
3167
1194
3359
2
3

5082
5082
1557
3564
11
4

4515
4650
6068
6068
186
436

7463
7463
7463
7687
34
115

Medium-grained locking
JVSTM

traversals
operations
traversals
operations
traversals
operations

Table 7.25: Maximum latency results for read-only short traversals and short operations with all the operations enabled and a read-write workload.

Finally, I show both in Table 7.21 through Table 7.23, and in Figure 7.25 through Figure 7.27, the results obtained when only the read-write long traversals are disabled. In
this case, the results for the JVSTM are even better, as it performs significantly better
than both the locking strategies except in the case of a write-dominated workload. These
results validate the suitability of the JVSTM for workloads with long read-only operations.

7.2.3.6

Latency Results

To conclude the evaluation of the JVSTM performance, I show, in Table 7.24 through Table 7.26, the results of the maximum latency for all the read-only short traversals and
short operations, for each of the various workloads, when all the operations are enabled.
These results show that, in the JVSTM, the execution of read-only transactions is not
affected by the remaining of the system. Whereas for the locking strategies there is a
significant increase of the maximum latency when the number of threads increases, that
phenomenon does not occur with the JVSTM. The problem with the locking strategies is
that the reads must wait that a lengthy write transaction (a long write traversal, typically)
finishes before they may proceed. In the JVSTM, however, the reads never need to wait.

7.3 Summary

201

Max latency by number of threads (ms)


Synchronization strategy

Read-only oper.

16

Coarse-grained locking

Short
Short
Short
Short
Short
Short

2
3
1
3
4
2

1652
569
489
474
0
3

5391
5720
1343
1030
1
34

5391
4011
3895
3564
6
6

4650
4515
6437
6437
4790
15

Medium-grained locking
JVSTM

traversals
operations
traversals
operations
traversals
operations

Table 7.26: Maximum latency results for read-only short traversals and short operations with all the operations enabled and a write-dominated workload.

7.3

Summary

This chapter presented the application of some of the proposals made in this dissertation
to a real-world large applicationthe Fnix systemwhich is developed by a team of
more than a dozen of programmers. The results obtained with this application show the
effectiveness of the proposals made in this dissertation, both in the reduction of problems
related to the implementation of a rich domain model, and the adequacy of the proposals
for real-world usage.
It presents, also, a brief performance evaluation of the JVSTM using more standard
benchmarks in the area of STMs. The results of this performance evaluation show that,
even though the JVSTM was not specifically designed as a high-performance STM implementation, it performs very well either in comparison with some of the best-known STM
implementations for Java, or in comparison with a baseline traditional locking strategy.
More specifically, the JVSTM is one to two orders of magnitude faster than the DSTM2
and an implementation of the ASTM, as reported in [Guerraoui et al., 2007].

202

Validation

Chapter 8

Conclusions
In this concluding chapter, I present the main contributions of this dissertation and
discuss some of the new research avenues that this work opens.

8.1

Main Contributions

The primary goal of the work presented in this dissertation was to simplify the implementation of rich object-oriented domain models. Moreover, that this should be accomplished
in a programmers friendly waythat is, in such a way that it is not only easily comprehensible by an average programmer, but also that it is practical to use in current software
development. I achieved this goal, because throughout this dissertation I identified some
of the problems with the existing approaches, I proposed solutions to those problems, and
I validated those solutions. So, the achievement of this goal is the primary contribution
of my work.
Yet, for achieving this goal, I made more specific contributions in several of the areas
addressed by this work.
In the area of Software Transactional Memory, I distinguish the following main contributions:

I proposed a new model of Software Transactional Memory that keeps multiple


versions for each transactional location. Even though the idea of multi-version
concurrency control is not new, this was the first application of this idea to the
area of Software Transactional Memory. This new multi-version model allows read
operations to execute both with less overheads and with no synchronization with
the remaining threads of a system. Moreover, this model provides the guarantee
that read-only transactions never fail.

204

Conclusions

I described in detail an implementation of the versioned STM model that is simple


to use, simple to implement without extra support from the execution runtime, and,
that, at the same time, performs one to two orders of magnitude better than some
of the best-known implementations of STM in Java.
I presented a lock-free algorithm for garbage-collecting the old values made unreachable during the execution of a versioned STM. This algorithm frees the old
values without having to perform costly sweepings of the memory, by leveraging
on the intimate knowledge about the workings of the STM that creates and make
inaccessible the transactional values. Even though at present this algorithm is implemented as part of a Java library and, therefore, cannot intervene directly in the
garbage collection process of the underlying Java runtime, it is reasonable to assume that this algorithm would be easily integrated into the Java runtime garbage
collection process with significant advantages for that process, if the JVSTM would
be implemented at the level of the Java virtual machine.

In the area of object-oriented programming, I distinguish the following main contributions:

I proposed a practical approach for integrating new programming constructs into


existing programming languages and practices. This approach is based on a set
of software-engineering-motivated guiding principles introduced in Section 1.2.2.
I argue that newly proposed programming constructs should integrate seamlessly
not only with existing programming languages, but also with the tools and practices that programmers use, so that the new constructs may be readily adopted by
programmers already trained in the existing programming languages.
I introduced the DML language as a language that allows the specification, in a
declarative way, of a domain models structure, and that integrates seamlessly with
the Java programming language. This language is simple to learn, not only because
its syntax is purposefully quite similar to the Javas syntax, but also because its
constructs correspond to well-established concepts used at the modeling level. In
particular, the DML supports the specification of entities and associations between
entities, both as first class constructs in the language. Even though this has been
done already over the years for various languages, the novelty of the DML is that it
is neither an extension of an existing language, nor an entirely new general-purpose
language meant to replace the existing languages. Rather, it is limited in expressiveness by design, and built to integrate with a mainstream programming language.
Its use in the development of a large real-world systemthe Fnix systemand
the readiness of its adoption by the programmers of that system demonstrates the
effectiveness of this approach.
I proposed, also, as part of the implementation of a domain specification described

8.2 Future Research

in the DML language, a new pattern for implementing associations in an objectoriented programming language. Compared to the existing patterns for implementing associations, my proposal has the advantage of being simpler to implement
(once we have all the supporting classes implemented). Furthermore, it provides
a friendlier interface to the programmer that is now able to create or remove association links in several different ways, including through the use of the familiar
interfaces of the Java Collection Framework. This new pattern allows, also, that
third-party programmersthat is, programmers that do not have access to the code
implementing the associationcustomize the behavior of the associations operations.
I introduced the idea of using consistency predicates to separate the implementation
of a domain models constraints from the code that updates the state of the domain
entities. By doing so, we reduce the code scattering, the code tangling, and the
strong coupling that results from the usual approach of implementing these two
concerns together. Moreover, this approach facilitates the composition of objects in
a truly object-oriented spirit, by allowing the composition of existing objects without
having to adapt them specifically to the new composition. This is made possible by
having the verification of the consistency predicates separate from the remaining
of the code, so that the composing object may control when will the consistency
predicates be evaluated, without knowledge or consent from the composed object.
I described an implementation of consistency predicates in the JVSTM that is sufficiently generic to be used without significant changes by most, if not all, other STM
implementations. This implementation does not require any significant change to
the underlying STM implementation. Instead, it leverages on the support for atomicity given by STMs to implement the evaluation of consistency predicates and the
necessary recording of dependencies.

Finally, in the area of software engineering, I contributed with a study of the typical
transactional workloads of a large real-world web applicationthe Fnix systemwhich is
representative of a large class of applications. This study was performed for an extended
period of time and gives us valuable insights about the needs of such kind of applications,
so that better solutions, suited to their specificities, may be developed.

8.2

Future Research

Even though the work described in this dissertation is self-contained, in the sense that it
may be readily used without further developments, it does not constitute, quite naturally,
the ultimate solution for any of the problems that it addresses. Rather on the contrary,
the decision to make proposals that are simple to learn and to implement makes these

205

206

Conclusions

proposals particularly susceptible to being extensible in many various ways.


In fact, I believe that it should be a goal of any research work to open up new research
directions. Therefore, I hope that others, as I intend myself, will follow-up on the work
that I describe in this dissertation.
In particular, I envision that the following topics, presented in no particular order, will
be pursued further in the future:
The implementation of the versioned STM at the virtual machine level. Implementing
the versioned STM at the virtual machine level, rather than at the higher-level of
the Java programming language, will allow for an entirely different set of design
decisions regarding its implementation. In particular, it should be possible to reduce
significantly the overhead incurred both in memory and in execution time by the use
of vboxes. Furthermore, related to this approach, it would make sense to integrate
the garbage collection of old unreachable values into the algorithms of garbage
collection of the existing runtimes.
The development of a lock-free version of the commit of a versioned STM transaction.
The commit operation is, in fact, embarrassingly parallel; so, a simple solution to
remove the current locking implementation would be to make all the threads that
want to commit to cooperate (or help) in the commit of other transactions. This
simple idea, however, is not trivial to put in practice with good results, because
other concerns, such as the amount of cache-coherency traffic generated by such
an approach may drive the performance of the system down. Thus, it is necessary
to investigate all the conflicting forces that may influence such a solution.
The development of data structures adequate for a versioned transactional memory.
There is much work and accumulated knowledge in data structures for parallel
computing, but the area of Transactional Memory introduces a significant amount of
changes that may render most of that knowledge useless in determining which data
structures are more adequate for a program that uses transactional memory. More
specifically, to make STMs more widely used in programming at large, we must give
programmers libraries of transactional classes such as collections, together with a
set of recommendations on which collections are best for each task. For a versioned
STM, in particular, a promising avenue of research is to study the applicability of
all the work made in purely functional persistent data structures.
The reduction of conflicts for write transactions in the versioned STM. Even though
the versioned STM is very good at eliminating conflicts for read-only transactions,
its performance degrades abruptly when the amount of conflicts for write transactions increases. Thus, a much needed line of future research is to develop new
alternatives to either reduce the conflicts, or to help in solving them. One possible
direction to follow is the integration of contention management into the versioned

8.2 Future Research

STM, which many other STMs use; the specifics of a versioned model, however,
may bring different design decisions into this integration. Another line of research
worthy of consideration is the use of an arbitrarily fine-grained structure of nested
transactions to allow the partial reexecution of a conflicting transaction. The idea,
in this case, is to avoid the whole reexecution of a conflicting transaction by reexecuting (eventually, with help from other threads) only the parts that may have
changed because of a conflict.
The extension of the DML language to support the specification of more aspects of
a domain models structure. The DML, as presented in this dissertation, allows
the representation of only a limited set of domain modeling constructs. Therefore,
it is to be expected that in the future other constructs be added to the language.
For instance, constructs to specify other kinds of associations, such as qualified
associations, or constructs to specify the refinement of entities or associations.
The extension of the design pattern for implementing associations. The pattern
introduced in this dissertation does not address other common requirements of
associations, such as that they should be ordered, or sorted, or even indexed by a
given attribute of one of the participating objects. Thus, an interesting and useful
line of research work would be the extension of this pattern to support such things.
The interaction between consistency predicates and other programming language
constructs. The integration of consistency predicates in a programming language
interferes with several other constructs that may exist in the language. For instance,
an obvious candidate is the system of exceptions of a language, given that consistency predicates may signal their failure through exceptions. Thus, it would be
interesting to see whether the exceptions thrown by consistency predicates should
deserve some kind of special treatment by the language. Or, even if not by the language, whether the distinction between exceptions thrown by consistency predicates
and other types of exceptions would be useful for a programmer, from a pragmatical
point of view.
More elaborate studies of the transactional workloads of real-world domain-intensive
applications. The study performed in this dissertation was rather simple, and served
mostly to validate the assumptions underlying the development of the versioned
STM. Yet, knowing in more detail how do real-world large applications use transactions may provide valuable feedback into the development of transactional systems.

These topics are, by no means, an exhaustive list of all the research work that may
follow from what is described in this dissertation. Rather, it is a list of some of the
topics on which I had already some thoughts, and with which I would like to finish my
dissertation.

207

208

Conclusions

Bibliography
Akehurst, D., Howells, G., and McDonald-Maier, K. Implementing associations: UML 2.0
to Java 5. Software and Systems Modeling, volume 6(1):pages 335, 2007.
Albano, A., Ghelli, G., and Orsini, R. A relationship mechanism for a strongly typed
object-oriented database programming language. In Proceedings of the 17th International Conference on Very Large Data Bases, pages 565575. 1991.
Alur, D., Crupi, J., and Malks, D. Core J2EE Patterns: Best Practices and Design Strategies. Prentice-Hall, Inc., New Jersey, USA, 2001.
ANSI and ITIC. American National Standard for information technology: programming
language Common LISP: ANSI X3.226-1994. American National Standards Institute,
1430 Broadway, New York, NY 10018, USA, 1996.
Arnold, K., Gosling, J., and Holmes, D. The Java Programming Language. Addison-Wesley,
Reading, Massachusetts, USA, third edition, 2000.
Barnett, M., DeLine, R., Fhndrich, M., Rustan, K., Leino, M., and Schulte, W. Verification of object-oriented programs with invariants. Journal of Object Technology,
volume 3(6):pages 2756, 2004. Special issue: ECOOP 2003 workshop on FTfJP.
Bartetzko, D. Parallelitt und Vererbung beim "Programmieren mit Vertrag" Weiterentwicklung von JaWA. Masters thesis, Universitt Oldenburg, 1999.
Bass, L., Clements, P., and Kazman, R. Software Architecture in Practice. SEI Series in
Software Engineering. Addison-Wesley, Reading, Massachusetts, USA, second edition,
2003.
Bernstein, P. A. and Goodman, N. Multiversion concurrency control theory and algorithms. ACM Transactions on Database Systems, volume 8(4):pages 465483, 1983.
Bierman, G. and Wren, A. First-class relationships in an object-oriented language. In
Proceedings of the 19th European Conference on Object-Oriented Programming, volume
3586 of Lecture Notes in Computer Science, pages 262286. Springer-Verlag, 2005.
Booch, G. Object-Oriented Analysis and Design with Applications. Addison-Wesley, Reading, Massachusetts, USA, second edition, 1994.

210

BIBLIOGRAPHY

Booch, G., Rumbaugh, J., and Jacobson, I. The Unified Modeling Language User Guide.
Addison-Wesley, 1999.
Cachopo, J. and Rito-Silva, A.

Versioned boxes as the basis for memory transac-

tions. In Workshop on Synchronization and Concurrency in Object-Oriented Languages


(SCOOL05). 2005. Available at http://hdl.handle.net/1802/2101.
Cachopo, J. and Rito-Silva, A. Versioned boxes as the basis for memory transactions.
Science of Computer Programming, volume 63(2):pages 172185, 2006.
Carey, M. J., DeWitt, D. J., and Naughton, J. F. The OO7 benchmark. SIGMOD Record
(ACM Special Interest Group on Management of Data), volume 22(2):pages 1221, 1993.
Cattell, R. G. G., Barry, D. K., Berler, M., Eastman, J., Jordan, D., Russell, C., Schadow,
O., Stanienda, T., and Velez, F., (Editors) The Object Data Standard ODMG 3.0. Morgan
Kaufmann Publishers, Inc., Los Altos, USA, 2000.
Chen, P. P.-S. The entity-relationship modeltoward a unified view of data. ACM Transactions on Database Systems, volume 1(1):pages 936, 1976.
Clarke, D. G., Potter, J. M., and Noble, J. Ownership types for flexible alias protection.
In Proceedings of the 13th ACM SIGPLAN Conference on Object-Oriented Programming,
Systems, Languages, and Applications, SIGPLAN Notices, pages 4864. ACM Press,
1998.
Clements, P., Bachmann, F., Bass, L., Garlan, D., Ivers, J., Little, R., Nord, R., and
Stafford, J. Documenting Software Architectures: Views and Beyond. SEI Series in
Software Engineering. Addison-Wesley, Reading, Massachusetts, USA, 2003.
Coplien, J. O. Advanced C++ Programming Styles and Idioms. Addison-Wesley, Reading,
Massachusetts, USA, 1992.
Czarnecki, K. and Eisenecker, U. W. Generative Programming: Methods, Tools, and Applications. Addison-Wesley, Reading, Massachusetts, USA, 2000.
Dietl, W. and Mller, P. Universes: Lightweight ownership for JML. Journal of Object
Technology, volume 4(8):pages 532, 2005.
DSTM2. Dynamic Software Transactional Memory Library 2.0. Visited in 2007. Home
page at http://www.sun.com/download/products.xml?id=453fb28e.
Evans, E. Domain-Driven Design: Tackling Complexity in the Heart of Software. AddisonWesley, Reading, Massachusetts, USA, 2003.
Evermann, J. and Wand, Y. Toward formalizing domain modeling semantics in language
syntax. IEEE Transactions on Software Engineering, volume 31(1):pages 2137, 2005.
FenixEDU. FnixEDU. 2005. Home page at http://fenixedu.sourceforge.net.

BIBLIOGRAPHY

Flanagan, C., Leino, K. R. M., Lillibridge, M., Nelson, G., Saxe, J. B., and Stata, R.
Extended static checking for java. SIGPLAN Not., volume 37(5):pages 234245, 2002.
Fowler, M. Patterns of Enterprise Application Architecture. Addison-Wesley, Reading,
Massachusetts, USA, 2002.
Fowler, M., Beck, K., Brant, J., Opdyke, W., and Roberts, D. Refactoring: Improving the
Design of Existing Code. Addison-Wesley, 1999.
France, R. B., Ghosh, S., Dinh-Trong, T., and Solberg, A. Model-driven development
using uml 2.0: Promises and pitfalls. Computer, volume 39(2):pages 5966, 2006.
Gabriel, R. P., Northrop, L., Schmidt, D. C., and Sullivan, K. Ultra-large-scale systems.
In Companion to the 21st ACM SIGPLAN Conference on Object-Oriented Programming,
Systems, Languages, and Applications, SIGPLAN Notices, pages 632634. ACM Press,
2006.
Gamma, E., Helm, R., Johnson, R., and Vlissides, J. Design Patterns: Elements of
Reusable Object-Oriented Software. Addison-Wesley, Reading, Massachusetts, USA,
1995.
Gnova, G., del Castillo, C. R., and Llorens, J. Mapping UML associations into java code.
Journal of Object Technology, volume 2(5):pages 135162, 2003.
Gnova, G., Llorens, J., and Martnez, P. The meaning of multiplicity of n-ary associations
in UML. Software and Systems Modeling, volume 1(2):pages 8697, 2002.
Gosling, J., Joy, B., Steele, G., and Bracha, G. The Java Language Specification. AddisonWesley, Reading, Massachusetts, USA, third edition, 2005.
Graham, P. and Barker, K. Effective optimistic concurrency control in multiversion object
bases. In Proceedings of the International Symposium on Object-Oriented Methodologies
and Systems, volume 858, pages 313328. Springer-Verlag, 1994.
Gueheneuc, Y.-G. and Albin-Amiot, H. A pragmatic study of binary class relationships.
In Proceedings of the 18th IEEE International Conference on Automated Software Engineering, pages 277280. 2003.
Guhneuc, Y.-G. and Albin-Amiot, H. Recovering binary class relationships: putting
icing on the UML cake. In Proceedings of the 19th ACM SIGPLAN Conference on ObjectOriented Programming, Systems, Languages, and Applications, volume 39 of SIGPLAN
Notices, pages 301314. ACM Press, 2004.
Guerraoui, R., Herlihy, M., and Pochon, S. Toward a theory of transactional contention
management. In Proceedings of the 24nd Annual ACM Symposium on Principles of
Distributed Computing. ACM Press, 2005.

211

212

BIBLIOGRAPHY

Guerraoui, R., Kapalka, M., and Vitek, J. STMBench7: A benchmark for software transactional memory. In Proceedings of the Second European Systems Conference. 2007.
Hailpern, B. and Tarr, P. Model-driven development: the good, the bad, and the ugly.
IBM Systems Journal, volume 45(3):pages 451461, 2006.
Harris, T. and Fraser, K. Language support for lightweight transactions. In Proceedings
of the 18th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, volume 36 of SIGPLAN Notices, pages 388402. ACM Press,
2003.
Harris, T., Marlowe, S., Peyton-Jones, S., and Herlihy, M. Composable memory transactions. In Proceedings of the ACM SIGPLAN Symposium on Principles and Practice of
Parallel Programming. ACM Press, 2005.
Harris, T. and Peyton-Jones, S. Transactional memory with data invariants. In First ACM
SIGPLAN Workshop on Languages, Compilers, and Hardware Support for Transactional
Computing. 2006.
Harrison, W., Barton, C., and Raghavachari, M. Mapping UML designs to Java. In
Proceedings of the 15th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, volume 35 of SIGPLAN Notices, pages 178187.
ACM Press, 2000.
Herlihy, M., Luchangco, V., and Moir, M. A flexible framework for implementing software
transactional memory. In Proceedings of the 21st Annual ACM SIGPLAN Conference
on Object-Oriented Programing, Systems, Languages, and Applications, pages 253262.
ACM Press, 2006.
Herlihy, M., Luchangco, V., Moir, M., and Scherer, III., W. N. Software transactional
memory for dynamic-sized data structures. In Proceedings of the 22nd Annual ACM
Symposium on Principles of Distributed Computing, pages 92101. ACM Press, 2003.
Herlihy, M. and Moss, J. E. B. Transactional memory: Architectural support for lockfree data structures. In Proceedings of the 20th Annual International Symposium on
Computer Architecture. 1993.
Herlihy, M. P. Wait-free synchronization. ACM Transactions on Programming Languages
and Systems, volume 13(1):pages 124149, 1991.
Herlihy, M. P. and Wing, J. M. Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, volume 12(3):pages
463492, 1990.
Hoare, C. A. R. Proof of correctness of data representations. Acta Informatica, volume 1:pages 271281, 1972.

BIBLIOGRAPHY

Iscoe, N., Williams, G. B., and Arango, G. Domain modeling for software engineering. In
Proceedings of the 13th International Conference on Software Engineering, pages 340
343. IEEE Computer Society, 1991.
Joy, B., Guy L. Steele, J., Gosling, J., and Bracha, G. The Java Language Specification.
Addison-Wesley, Reading, Massachusetts, USA, second edition, 2000.
JVSTM. JVSTM. 2005. Home page at http://www.esw.inesc-id.pt/~jcachopo/

jvstm.
Karaorman, M., Hlzle, U., and Bruno, J. jContractor: A reflective java library to support
Design by Contract. In Proceedings of the 2nd International Conference on Meta-Level
Architectures and Reflection, number 1616 in Lecture Notes in Computer Science, pages
175196. Springer-Verlag, 1999.
Kiczales, G., Lamping, J., Menhdhekar, A., Maeda, C., Lopes, C., Loingtier, J.-M., and
Irwin, J. Aspect-oriented programming. In M. Aksit and S. Matsuoka, (Editors) Proceedings of the 11th European Conference on Object-Oriented Programming, volume 1241 of
Lecture Notes in Computer Science, pages 220242. Springer-Verlag, 1997.
Kramer, R. iContract the Java Design by Contract tool. In Proceedings of the TOOLS98:
Technology of Object-Oriented Languages and Systems, pages 295307. IEEE Computer
Society, 1998.
Lackner, M., Krall, A., and Puntigam, F. Supporting design by contract in java. Journal
of Object Technology, volume 1(3):pages 5776, 2002. Special issue: TOOLS USA 2002
proceedings.
Leavens, G. T., Cheon, Y., Clifton, C., Ruby, C., and Cok, D. R. How the design of JML
accommodates both runtime assertion checking and formal verification. Science of
Computer Programming, volume 55:pages 185208, 2005.
Leavens, G. T., Ruby, C., Rustan, K., Leino, M., Poll, E., and Jacobs, B. Jml (poster session): notations and tools supporting detailed design in java. In OOPSLA 00: Addendum to the 2000 proceedings of the conference on Object-oriented programming, systems,
languages, and applications (Addendum), pages 105106. ACM Press, 2000.
Manson, J., Pugh, W., and Adve, S. V. The java memory model. In Conference Record
of the 32th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages,
pages 378391. ACM Press, 2005.
Marathe, V. J., Scherer, W. N., and Scott, M. L. Design tradeoffs in modern software
transactional memory systems. In Proceedings of the 7th Workshop on Languages,
Compilers, and Run-Time Support for Scalable Systems, pages 17. ACM Press, 2004.
Marathe, V. J. and Scott, M. L. A qualitative survey of modern software transactional
memory systems. Technical Report UR CSD;TR 839, 2004.

213

214

BIBLIOGRAPHY

Mellor, S. J., Scott, K., Uhl, A., and Weise, D. MDA Distilled. Addison-Wesley, Reading,
Massachusetts, USA, 2004.
Meyer, B. Object-Oriented Software Construction. Prentice-Hall, Inc., New Jersey, USA,
1988.
Meyer, B. Applying design by contract. Computer, volume 25(10):pages 4051, 1992a.
Meyer, B. Eiffel: The Language. Prentice-Hall, Inc., New Jersey, USA, 1992b.
Moss, J. E. B. and Hosking, A. L. Nested transactional memory: Model and preliminary architecture sketches.

In Workshop on Synchronization and Concurrency in

Object-Oriented Languages (SCOOL05). 2005.

Available at http://hdl.handle.

net/1802/2099.
Mller, P., Poetzsch-Heffter, A., and Leavens, G. T. Modular invariants for layered object
structures. Science of Computer Programming, volume 62(3):pages 253286, 2006.
Noble, J. Basic relationship patterns. volume 4, chapter 6, pages 7394. Addison-Wesley,
Reading, Massachusetts, USA, 2000.
Noble, J. and Grundy, J. Explicit relationships in object-oriented development. 1995.
Object Management Group. Unified modeling language: Superstructure (version 2.0).
Available at http://www.omg.org/cgi-bin/doc?formal/05-07-04, Visited in
2007.
OJB. Object/Relational Bridge OJB. Visited in 2007. Home page at http://db.

apache.org/ojb.
Okasaki, C. Purely Functional Data Structures. Cambridge University Press, Cambridge,
MA, USA, 1998.
OMG. OMG Model Driven Architecture. 2007. Home page at http://www.omg.org/

mda.
Parnas, D. L. On the criteria to be used in decomposing systems into modules. Communications of the ACM, volume 15(12):pages 10531058, 1972.
Pearce, D. J. and Noble, J. Relationship aspects. In Proceedings of the 5th International
Conference on Aspect-Oriented Software Development, pages 7586. ACM Press, 2006.
Polak, B., (Editor) Ultra-Large-Scale Systems: The Software Challenge of the Future. Software Engineering Institute, Carnegie Mellon, Pittsburgh, USA, 2006.

Available at

http://www.sei.cmu.edu/uls/.
Pugh, W. Fixing the java memory model. In Proceedings of the ACM 1999 Conference on
Java Grande, pages 8998. ACM Press, 1999.

BIBLIOGRAPHY

215

Raistrick, C., Francis, P., and Wright, J. Model Driven Architecture with Executable UML.
Cambridge University Press, Cambridge, MA, USA, 2004.
Reed, D. P. Naming and Synchronization in a Decentralized Computer System. Ph.D.
thesis, MIT, Cambridge, MA, USA, 1978.
Reed, D. P. Implementing atomic actions on decentralized data. ACM Transactions on
Computer Systems, volume 1(1):pages 323, 1983.
Riegel, T., Felber, P., and Fetzer, C. A lazy snapshot algorithm with eager validation. In
20th International Symposium on Distributed Computing (DISC). 2006a.
Riegel, T., Fetzer, C., and Felber, P. Snapshot isolation for software transactional memory.
In First ACM SIGPLAN Workshop on Languages, Compilers, and Hardware Support for
Transactional Computing. 2006b.
Riehle, D. Framework Design: A Role Modeling Approach. Ph.D. thesis, Swiss Federal
Institute of Technology Zurich, 2000.
Royce, W. W. Managing the development of large software systems: concepts and techniques. In Proceedings of the 9th International Conference on Software Engineering,
pages 328338. IEEE Computer Society, 1987.
Rumbaugh, J. Relations as semantic constructs in an object-oriented language. In Proceedings of the 2nd ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, volume 22 of SIGPLAN Notices, pages 466481.
ACM Press, 1987.
Scherer III, W. N. and Scott, M. L. Contention management in dynamic software transactional memory. In Proceedings of the ACM PODC Workshop on Concurrency and
Synchronization in Java Programs. 2004.
Scherer III, W. N. and Scott, M. L. Advanced contention management for dynamic software transactional memoryy. In Proceedings of the 24nd Annual ACM Symposium on
Principles of Distributed Computing. ACM Press, 2005.
Selic, B. The pragmatics of model-driven development. IEEE Software, volume 20(5):pages
1925, 2003.
Shah, A. V., Hamel, J. H., Borsari, R. A., and Rumbaugh, J. E.

DSM: an object-

relationship modeling language. In Proceedings of the 4th ACM SIGPLAN Conference


on Object-Oriented Programming, Systems, Languages, and Applications, SIGPLAN Notices, pages 191202. ACM Press, 1989.
Shavit, N. and Touitou, D. Software transactional memory. In Proceedings of the 14th
Annual ACM Symposium on Principles of Distributed Computing, pages 204213. ACM
Press, 1995.

216

BIBLIOGRAPHY

Suscheck, C. A. and Sandn, B. A construct for effectively implementing semantic associations. Journal of Object Technology, volume 2(3):pages 101111, 2003.
Thomas, D. UML - Unified or Universal Modeling Language? Journal of Object Technology,
volume 2(1):pages 712, 2003.
Thomas, D. and Barry, B. M. Model driven development: the case for domain oriented
programming. In Companion to the 18th ACM SIGPLAN Conference on Object-Oriented
Programming, Systems, Languages, and Applications, SIGPLAN Notices, pages 27. ACM
Press, 2003.

Vous aimerez peut-être aussi