SlideShare a Scribd company logo
public class Group {
public Guid Id { get; set; }
public string Name { get; set; }
public string Objective { get; set; }
public GroupType Type { get; set; }
//...
}
//Usage
var @group = new Group {
Id = message.GroupId,
Name = message.Name,
Objective = message.Objective,
Type = GroupType.Private
};
public class Group {
public Group(
Guid id,
string name,
string objective,
GroupType type)
{
//...
}
}
//Usage
var @group = new Group(
message.GroupId,
message.Name,
message.Objective,
GroupType.Private
);
public class PrivateGroup {
private PrivateGroup(/* ... */)
{
/* ... */
}
public static PrivateGroup Start(
GroupId id,
GroupName name,
GroupObjective objective)
{
//...
}
}
//Usage
var @group = PrivateGroup.Start(
new GroupId(message.GroupId),
new GroupName(message.Name),
GroupObjective.InferFrom(message.Objective)
);
public class Group {
public Group(
Guid id,
string name,
string objective,
GroupType type)
{
if(id == Guid.Empty)
throw new ArgumentException("...", "id");
if(string.IsNullOrEmpty(name))
throw new ArgumentException("...", "name");
if(name.Length > 255)
throw new ArgumentException("...", "name");
if(objective != null && objective.Length == 0)
throw new ArgumentException("...", "objective");
if(objective != null && objective.Length > 500)
throw new ArgumentException("...", "objective");
if(type != GroupType.Private && type != GroupType.Public)
throw new ArgumentException("...", "type");
//...
}
}
public class PrivateGroup {
public static PrivateGroup Start(
GroupId id,
GroupName name,
GroupObjective objective)
{
//...
}
}
public class GroupId {
private readonly Guid value;
public GroupId(Guid value)
{
if(value == Guid.Empty)
throw new ArgumentException("...", "value");
this.value = value;
}
public override bool Equals(object other)
{
if(other == null || other.GetType() != this.GetType()) return false;
return ((GroupId)other).value.Equals(this.value);
}
public override int GetHashCode()
{
return this.value.GetHashCode();
}
//...
}
public class GroupName {
private readonly string value;
public GroupName(string value)
{
if(string.IsNullOrEmpty(value))
throw new ArgumentException("...", "value");
if(value.Length > Metadata.MaximumGroupNameLength)
throw new ArgumentException("...", "value");
this.value = value;
}
//...
}
//Usage
@group.Rename(new GroupName(message.Name));
public class GroupObjective {
private static readonly GroupObjective NotSpecified = new GroupObjective(null);
private readonly string value;
private GroupObjective(string value)
{
this.value = value;
}
public static GroupObjective InferFrom(string value)
{
if(value == null) return NotSpecified;
if(value.Length == 0)
throw new ArgumentException("...", "value");
if(value.Length > Metadata.MaximumGroupObjectiveLength)
throw new ArgumentException("...", "value");
return new GroupObjective(value);
}
}
public class GroupObjective {
//...
public static GroupObjective InferFromMarkdown(string value)
{
if(value == null) return NotSpecified;
MarkDownDocument parsedDocument;
if(!MarkDownParser.TryParse(value, out parsedDocument))
throw new ArgumentException("...", "value");
var document = parsedDocument.Sanitize();
if(document.Text.Length == 0)
throw new ArgumentException("...", "value");
if(document.Text.Length > Metadata.MaximumGroupObjectiveLength)
throw new ArgumentException("...", "value");
return new GroupObjective(document.ToString());
}
}
public class GroupObjective {
private static readonly GroupObjective NotSpecified =
new GroupObjective(MimeType.PlainText, null);
private readonly MimeType mimeType;
private readonly string value;
private GroupObjective(MimeType mimeType, string value)
{
this.mimeType = mimeType;
this.value = value;
}
public static GroupObjective InferFromMarkdown(MarkDownDocument value)
{
if(value == null) return NotSpecified;
var document = value.Sanitize();
if(document.Text.Length == 0)
throw new ArgumentException("...", "value");
if(document.Text.Length > Metadata.MaximumGroupObjectiveLength)
throw new ArgumentException("...", "value");
return new GroupObjective(MimeType.Markdown, document.ToString());
}
}
public class GroupObjective {
public static GroupObjective InferFromMarkdown(SanitizedMarkDownDocument document)
{
if(value == null) return NotSpecified;
if(document.Text.Length == 0)
throw new ArgumentException("...", "value");
if(document.Text.Length > Metadata.MaximumGroupObjectiveLength)
throw new ArgumentException("...", "value");
return new GroupObjective(MimeType.Markdown, document.ToString());
}
}
public class FillPercentage {
private readonly double value;
public FillPercentage(double value)
{
if(value < 0)
throw new ArgumentOutOfRangeException("value", value,
"The fill percentage must be greater than or equal to 0.");
this.value = value;
}
public static FillPercentage FromPercentNotation(double value)
{
return new FillPercentage(value / 100d);
}
public static implicit operator double(FillPercentage instance)
{
return instance.value;
}
}
public class MaximumFillPercentage {
private readonly double value;
public MaximumFillPercentage(double value)
{
if(value < 1)
throw new ArgumentOutOfRangeException("value", value,
"The maximum fill percentage must be greater than or equal to 1.");
this.value = value;
}
public static MaximumFillPercentage FromPercentNotation(double value)
{
return new MaximumFillPercentage(value / 100d);
}
public static implicit operator double(MaximumFillPercentage instance)
{
return instance.value;
}
}
public class MaximumFillPercentage : IComparable<FillPercentage> {
//...
public int CompareTo(FillPercentage other)
{
return this.value.Compare(other);
}
}
public class MaximumFillPercentage : IComparable<FillPercentage> {
//...
public static bool operator <
(MaximumFillPercentage left, FillPercentage right) {
return left.CompareTo(right) == -1;
}
public static bool operator <=
(MaximumFillPercentage left, FillPercentage right) {
return left.CompareTo(right) <= 0;
}
public static bool operator >
(MaximumFillPercentage left, FillPercentage right) {
return left.CompareTo(right) == 1;
}
public static bool operator >=
(MaximumFillPercentage left, FillPercentage right) {
return left.CompareTo(right) >= 0;
}
}
public class FillPercentage {
//...
public bool Exceeds(MaximumFillPercentage maximum) {
return maximum < this;
}
}
public class Session
{
// ...
public Session Add(Timeslot timeslot)
{
return new Session(this.StartTime, this.EndTime,
this.MaximumFillPercentage, this.Timeslots.Add(timeslot));
}
public MaximumFillPercentage MaximumFillPercentage { get; }
public FillPercentage CurrentFillPercentage
{
get
{
var sessionDuration = this.EndTime.Subtract(this.StartTime);
var totalTimeslotDuration = this.Timeslots.Aggregate(Duration.Zero,
(current, timeslot) =>
current.Add(timeslot.DurationBetween(this.StartTime, this.EndTime)))
return new FillPercentage(
totalTimeslotDuration / sessionDuration);
}
}
}
public class ScheduleDay
{
public void Book(AppointmentTimeslot timeslot)
{
var beforeBooking = FindSessionToBookOn(timeslot);
var afterBooking = beforeBooking.Add(timeslot);
if (afterBooking
.CurrentFillPercentage
.Exceeds(beforeBooking.MaximumFillPercentage))
{
throw new MaximumFillPercentageExceededException(/* ... */);
}
// ...
}
}
•
•
•
•
•
•
•
•
•
•
•
public class PrivateGroupBehavior : BehaviorModule {
public PrivateGroupBehavior(
ITenantRepository tenantRepository,
IPrivateGroupRepository groupRepository)
{
Receive<StartPrivateGroup>(async message =>
{
var tenant = await tenantRepository.Get(new TenantId(message.TenantId));
var @group = tenant.StartPrivateGroup(
new GroupId(message.GroupId),
new GroupName(message.GroupName);
GroupObjective.InferFromMarkdown(message.GroupObjective));
groupRepository.Add(@group);
});
}
}
public class PrivateGroupScenarios {
[Fact] public async Task when_starting_a_private_group() {
using(var context = new GroupContext()) {
//Arrange
context.Tenants.Add(new TenantData { TenantId = 123, Subdomain = "nespresso" });
var module = new PrivateGroupBehavior(
new TenantRepository(context),
new PrivateGroupRepository(context)
);
//Act
await module.Send(new StartPrivateGroup {
TenantId = 123,
GroupId = new Guid("a9a620ca-c8c2-41b7-b7c0-fc03881ed00d"),
Name = "Prototype Retail Machines"
});
//Assert
Assert.Equal(1, context.PrivateGroups.Count());
GroupData actual = context.PrivateGroups.Single();
Assert.Equal(123, actual.TenantId);
Assert.Equal(new Guid("a9a620ca-c8c2-41b7-b7c0-fc03881ed00d"), actual.GroupId);
Assert.Equal("Prototype Retail Machines", actual.Name);
}
}
}
public class PrivateGroupScenarios {
[Fact] public Task when_starting_a_private_group() {
var tenantId = new Random().Next(1, 100);
var groupId = Guid.NewGuid();
return new Scenario()
.Given(tenantId, new TenantSnapshot {
TenantId = tenantId,
Subdomain = "nespresso"
})
.When(new StartPrivateGroup {
TenantId = tenantId,
GroupId = groupId,
Name = "Prototype Retail Machines"
})
.Then(groupId, new PrivateGroupSnapshot {
TenantId = tenantId,
GroupId = groupId,
Name = "Prototype Retail Machines"
})
.Assert(); // <- the boring stuff happens here
} //example of testing a state-based model
}
public class PrivateGroupScenarios {
[Fact] public Task when_starting_a_private_group() {
var tenantId = new Random().Next(1, 100);
var groupId = Guid.NewGuid();
return new Scenario()
.Given(tenantId, new RegisteredAsTenant {
TenantId = tenantId,
Subdomain = "nespresso"
})
.When(new StartPrivateGroup {
TenantId = tenantId,
GroupId = groupId,
Name = "Prototype Retail Machines"
})
.Then(groupId, new PrivateGroupStarted {
TenantId = tenantId,
GroupId = groupId,
Name = "Prototype Retail Machines"
})
.Assert(); // <- the boring stuff happens here
} //example of testing a event-driven model
}
public class PrivateGroupScenarios {
[Fact] public Task when_starting_a_private_group() {
var tenantId = new TenantId(new Random().Next(1, 100));
var subdomain = new Subdomain("nespresso");
var groupId = new GroupId(Guid.NewGuid());
var groupName = new GroupName("Prototype Retail Machines");
return new Scenario()
.Given(tenantId, new RegisteredAsTenant {
TenantId = tenantId,
Subdomain = subdomain
})
.When(new StartPrivateGroup {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Then(groupId, new PrivateGroupStarted {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Assert();
} //introducing value objects in your tests
}
public class PrivateGroupScenarios {
[Fact] public Task when_starting_a_private_group() {
var tenantId = new TenantId(new Random().Next(1, 100));
var subdomain = new Subdomain("nespresso");
var groupId = new GroupId(Guid.NewGuid());
var groupName = new GroupName("Prototype Retail Machines");
return new Scenario()
.Given(tenantId, new RegisteredAsTenant {
TenantId = tenantId,
Subdomain = subdomain
})
.When(new StartPrivateGroup {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Then(groupId, new PrivateGroupStarted {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Assert();
} //introducing value objects in your tests
}
public class PrivateGroupScenarios {
[Fact] public Task when_starting_a_private_group() {
var fixture = new ScenarioFixture();
var tenantId = fixture.Create<TenantId>();
var groupId = fixture.Create<GroupId>();
var groupName = fixture.Create<GroupName>();
return new Scenario()
.Given(tenantId, new RegisteredAsTenant {
TenantId = tenantId, Subdomain = fixture.Create<Subdomain>()
})
.When(new StartPrivateGroup {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Then(groupId, new PrivateGroupStarted {
TenantId = tenantId,
GroupId = groupId,
Name = groupName
})
.Assert();
} //generate anonymous, random test values
}
•
•
•
•
•
public class MembershipRequest {
public void Approve(PersonId approver) {
if (this.Archived)
throw new MembershipRequestWasArchived(this.GroupId, this.RequestId);
if (this.Declined)
throw new MembershipRequestWasDeclined(this.GroupId, this.RequestId);
if (this.Cancelled)
throw new MembershipRequestWasCancelled(this.GroupId, this.RequestId);
if (this.Approved) return;
// ... happy path ...
}
}
public class MembershipRequest {
private enum State {
Requested, Approved, Declined, Cancelled, Archived
}
public void Approve(PersonId approver) {
if (this.CurrentState == State.Archived)
throw new MembershipRequestWasArchived(this.GroupId, this.RequestId);
if (this.CurrentState == State.Declined)
throw new MembershipRequestWasDeclined(this.GroupId, this.RequestId);
if (this.CurrentState == State.Cancelled)
throw new MembershipRequestWasCancelled(this.GroupId, this.RequestId);
if (this.CurrentState == State.Approved) return;
// ... happy path ...
this.CurrentState = State.Approved;
}
}
public class MembershipRequest {
private enum State {
Initial, Requested, Approved, Declined, Cancelled, Archived
}
private enum Trigger {
Request, Approve, Decline, Cancel, Archive
}
public void Approve(PersonId approver) {
this.Machine.Fire(Trigger.Approve); // <- Magic
// ... happy path ...
}
}
public class MembershipRequest {
public MembershipRequest() { this.Machine = ConfigureStatemachine(); }
private StateMachine<State, Trigger> ConfigureStatemachine() {
return new StateMachine<State, Trigger>(State.Initial)
.Configure(State.Initial).Permit(Trigger.Request, State.Requested)
.Configure(State.Requested)
.Permit(Trigger.Approve, State.Approved)
.Permit(Trigger.Decline, State.Declined)
.Permit(Trigger.Cancel, State.Cancelled)
.Permit(Trigger.Archive, State.Archived)
.Configure(State.Approved).Ignore(Trigger.Approve)
.GuardAnyOf(Trigger.Cancel, Trigger.Decline, Trigger.Archive,
new MembershipRequestWasApproved(this.GroupId, this.RequestId))
.Configure(State.Cancelled).Ignore(Trigger.Cancel)
.GuardAnyOf(Trigger.Approve, Trigger.Decline, Trigger.Archive,
new MembershipRequestWasCancelled(this.GroupId, this.RequestId))
.Configure(State.Declined).Ignore(Trigger.Decline)
.GuardAnyOf(Trigger.Approve, Trigger.Cancel, Trigger.Archive,
new MembershipRequestWasDeclined(this.GroupId, this.RequestId))
.Configure(State.Archived).Ignore(Trigger.Archive)
.GuardAnyOf(Trigger.Approve, Trigger.Cancel, Trigger.Decline,
new MembershipRequestWasArchived(this.GroupId, this.RequestId));
}
}
•
•
•
•
Design in the small

More Related Content

What's hot

GeeCON Prague 2014 - Metaprogramming with Groovy
GeeCON Prague 2014 - Metaprogramming with GroovyGeeCON Prague 2014 - Metaprogramming with Groovy
GeeCON Prague 2014 - Metaprogramming with Groovy
Iván López Martín
 
An introduction into Spring Data
An introduction into Spring DataAn introduction into Spring Data
An introduction into Spring Data
Oliver Gierke
 
Java căn bản - Chapter2
Java căn bản - Chapter2Java căn bản - Chapter2
Java căn bản - Chapter2Vince Vo
 
Zend Framework 1 + Doctrine 2
Zend Framework 1 + Doctrine 2Zend Framework 1 + Doctrine 2
Zend Framework 1 + Doctrine 2
Ralph Schindler
 
Slice for Distributed Persistence (JavaOne 2010)
Slice for Distributed Persistence (JavaOne 2010)Slice for Distributed Persistence (JavaOne 2010)
Slice for Distributed Persistence (JavaOne 2010)
Pinaki Poddar
 
11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objectsPhúc Đỗ
 
Java class 4
Java class 4Java class 4
Java class 4Edureka!
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
Jeevesh Pandey
 
Java OOP Programming language (Part 8) - Java Database JDBC
Java OOP Programming language (Part 8) - Java Database JDBCJava OOP Programming language (Part 8) - Java Database JDBC
Java OOP Programming language (Part 8) - Java Database JDBC
OUM SAOKOSAL
 
Network vs. Code Metrics to Predict Defects: A Replication Study
Network vs. Code Metrics  to Predict Defects: A Replication StudyNetwork vs. Code Metrics  to Predict Defects: A Replication Study
Network vs. Code Metrics to Predict Defects: A Replication StudyKim Herzig
 
Hibernate online training
Hibernate online trainingHibernate online training
Hibernate online training
QUONTRASOLUTIONS
 
Modul Praktek Java OOP
Modul Praktek Java OOP Modul Praktek Java OOP
Modul Praktek Java OOP
Zaenal Arifin
 
Advanced php
Advanced phpAdvanced php
Advanced phphamfu
 
Object oriented programming in python
Object oriented programming in pythonObject oriented programming in python
Object oriented programming in python
baabtra.com - No. 1 supplier of quality freshers
 
Advance Java Programs skeleton
Advance Java Programs skeletonAdvance Java Programs skeleton
Advance Java Programs skeleton
Iram Ramrajkar
 

What's hot (18)

GeeCON Prague 2014 - Metaprogramming with Groovy
GeeCON Prague 2014 - Metaprogramming with GroovyGeeCON Prague 2014 - Metaprogramming with Groovy
GeeCON Prague 2014 - Metaprogramming with Groovy
 
An introduction into Spring Data
An introduction into Spring DataAn introduction into Spring Data
An introduction into Spring Data
 
Java căn bản - Chapter2
Java căn bản - Chapter2Java căn bản - Chapter2
Java căn bản - Chapter2
 
Zend Framework 1 + Doctrine 2
Zend Framework 1 + Doctrine 2Zend Framework 1 + Doctrine 2
Zend Framework 1 + Doctrine 2
 
Spock and Geb
Spock and GebSpock and Geb
Spock and Geb
 
Slice for Distributed Persistence (JavaOne 2010)
Slice for Distributed Persistence (JavaOne 2010)Slice for Distributed Persistence (JavaOne 2010)
Slice for Distributed Persistence (JavaOne 2010)
 
Clojure: a LISP for the JVM
Clojure: a LISP for the JVMClojure: a LISP for the JVM
Clojure: a LISP for the JVM
 
11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objects
 
Java class 4
Java class 4Java class 4
Java class 4
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
 
Java OOP Programming language (Part 8) - Java Database JDBC
Java OOP Programming language (Part 8) - Java Database JDBCJava OOP Programming language (Part 8) - Java Database JDBC
Java OOP Programming language (Part 8) - Java Database JDBC
 
Network vs. Code Metrics to Predict Defects: A Replication Study
Network vs. Code Metrics  to Predict Defects: A Replication StudyNetwork vs. Code Metrics  to Predict Defects: A Replication Study
Network vs. Code Metrics to Predict Defects: A Replication Study
 
Hibernate online training
Hibernate online trainingHibernate online training
Hibernate online training
 
Modul Praktek Java OOP
Modul Praktek Java OOP Modul Praktek Java OOP
Modul Praktek Java OOP
 
Advanced php
Advanced phpAdvanced php
Advanced php
 
Object oriented programming in python
Object oriented programming in pythonObject oriented programming in python
Object oriented programming in python
 
9 cm604.28
9 cm604.289 cm604.28
9 cm604.28
 
Advance Java Programs skeleton
Advance Java Programs skeletonAdvance Java Programs skeleton
Advance Java Programs skeleton
 

Similar to Design in the small

Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
.NET Conf UY
 
Java2
Java2Java2
What’s new in C# 6
What’s new in C# 6What’s new in C# 6
What’s new in C# 6
Fiyaz Hasan
 
Java Concurrency Gotchas
Java Concurrency GotchasJava Concurrency Gotchas
Java Concurrency Gotchas
Alex Miller
 
3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java
DEVTYPE
 
C h 04 oop_inheritance
C h 04 oop_inheritanceC h 04 oop_inheritance
C h 04 oop_inheritanceshatha00
 
Java осень 2012 лекция 2
Java осень 2012 лекция 2Java осень 2012 лекция 2
Java осень 2012 лекция 2Technopark
 
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)潤一 加藤
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
Daniel Wellman
 
Refactoring - Mejorando el diseño del código existente
Refactoring - Mejorando el diseño del código existenteRefactoring - Mejorando el diseño del código existente
Refactoring - Mejorando el diseño del código existente
Mariano Sánchez
 
Lombokの紹介
Lombokの紹介Lombokの紹介
Lombokの紹介
onozaty
 
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
argsstring
 
This is a java lab assignment. I have added the first part java re.pdf
This is a java lab assignment. I have added the first part java re.pdfThis is a java lab assignment. I have added the first part java re.pdf
This is a java lab assignment. I have added the first part java re.pdf
feetshoemart
 
OOP Lab Report.docx
OOP Lab Report.docxOOP Lab Report.docx
OOP Lab Report.docx
ArafatSahinAfridi
 
Twig tips and tricks
Twig tips and tricksTwig tips and tricks
Twig tips and tricks
Javier Eguiluz
 
Lecture 17 - PHP-Object-Orientation.pptx
Lecture 17 - PHP-Object-Orientation.pptxLecture 17 - PHP-Object-Orientation.pptx
Lecture 17 - PHP-Object-Orientation.pptx
DavidLazar17
 
Codemotion appengine
Codemotion appengineCodemotion appengine
Codemotion appengine
Ignacio Coloma
 
Java Methods
Java MethodsJava Methods
Java Methods
Rosmina Joy Cabauatan
 

Similar to Design in the small (20)

Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
Code Smells y Refactoring o haciendo que nuestro codigo huela (y se vea) mejo...
 
Java2
Java2Java2
Java2
 
What’s new in C# 6
What’s new in C# 6What’s new in C# 6
What’s new in C# 6
 
Java Concurrency Gotchas
Java Concurrency GotchasJava Concurrency Gotchas
Java Concurrency Gotchas
 
3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java
 
C h 04 oop_inheritance
C h 04 oop_inheritanceC h 04 oop_inheritance
C h 04 oop_inheritance
 
Java осень 2012 лекция 2
Java осень 2012 лекция 2Java осень 2012 лекция 2
Java осень 2012 лекция 2
 
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)
第1回 チキチキ『( ゜ェ゜)・;'.、ゴフッ』 - シングルトンパターン(Java)
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
Refactoring - Mejorando el diseño del código existente
Refactoring - Mejorando el diseño del código existenteRefactoring - Mejorando el diseño del código existente
Refactoring - Mejorando el diseño del código existente
 
Lombokの紹介
Lombokの紹介Lombokの紹介
Lombokの紹介
 
Java Reflection
Java ReflectionJava Reflection
Java Reflection
 
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
Java for the Impatient, Java Constructors, Scope, Wrappers, Inheritance, and ...
 
This is a java lab assignment. I have added the first part java re.pdf
This is a java lab assignment. I have added the first part java re.pdfThis is a java lab assignment. I have added the first part java re.pdf
This is a java lab assignment. I have added the first part java re.pdf
 
OOP Lab Report.docx
OOP Lab Report.docxOOP Lab Report.docx
OOP Lab Report.docx
 
Twig tips and tricks
Twig tips and tricksTwig tips and tricks
Twig tips and tricks
 
Lecture 17 - PHP-Object-Orientation.pptx
Lecture 17 - PHP-Object-Orientation.pptxLecture 17 - PHP-Object-Orientation.pptx
Lecture 17 - PHP-Object-Orientation.pptx
 
Codemotion appengine
Codemotion appengineCodemotion appengine
Codemotion appengine
 
Oop lecture9 11
Oop lecture9 11Oop lecture9 11
Oop lecture9 11
 
Java Methods
Java MethodsJava Methods
Java Methods
 

Recently uploaded

Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
Fermin Galan
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
Globus
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
Cyanic lab
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
Roshan Dwivedi
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
Ortus Solutions, Corp
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Natan Silnitsky
 
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Globus
 
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing SuiteAI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
Google
 
Top 7 Unique WhatsApp API Benefits | Saudi Arabia
Top 7 Unique WhatsApp API Benefits | Saudi ArabiaTop 7 Unique WhatsApp API Benefits | Saudi Arabia
Top 7 Unique WhatsApp API Benefits | Saudi Arabia
Yara Milbes
 
How to Position Your Globus Data Portal for Success Ten Good Practices
How to Position Your Globus Data Portal for Success Ten Good PracticesHow to Position Your Globus Data Portal for Success Ten Good Practices
How to Position Your Globus Data Portal for Success Ten Good Practices
Globus
 
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
wottaspaceseo
 
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptxText-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
ShamsuddeenMuhammadA
 
2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx
Georgi Kodinov
 
Enhancing Research Orchestration Capabilities at ORNL.pdf
Enhancing Research Orchestration Capabilities at ORNL.pdfEnhancing Research Orchestration Capabilities at ORNL.pdf
Enhancing Research Orchestration Capabilities at ORNL.pdf
Globus
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
Juraj Vysvader
 
Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...
Globus
 
Understanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSageUnderstanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSage
Globus
 
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdfDominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
AMB-Review
 

Recently uploaded (20)

Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
 
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
 
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing SuiteAI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
 
Top 7 Unique WhatsApp API Benefits | Saudi Arabia
Top 7 Unique WhatsApp API Benefits | Saudi ArabiaTop 7 Unique WhatsApp API Benefits | Saudi Arabia
Top 7 Unique WhatsApp API Benefits | Saudi Arabia
 
How to Position Your Globus Data Portal for Success Ten Good Practices
How to Position Your Globus Data Portal for Success Ten Good PracticesHow to Position Your Globus Data Portal for Success Ten Good Practices
How to Position Your Globus Data Portal for Success Ten Good Practices
 
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
 
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptxText-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
Text-Summarization-of-Breaking-News-Using-Fine-tuning-BART-Model.pptx
 
2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx
 
Enhancing Research Orchestration Capabilities at ORNL.pdf
Enhancing Research Orchestration Capabilities at ORNL.pdfEnhancing Research Orchestration Capabilities at ORNL.pdf
Enhancing Research Orchestration Capabilities at ORNL.pdf
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
 
Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...
 
Understanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSageUnderstanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSage
 
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdfDominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
Dominate Social Media with TubeTrivia AI’s Addictive Quiz Videos.pdf
 

Design in the small

  • 1.
  • 2.
  • 3.
  • 4. public class Group { public Guid Id { get; set; } public string Name { get; set; } public string Objective { get; set; } public GroupType Type { get; set; } //... } //Usage var @group = new Group { Id = message.GroupId, Name = message.Name, Objective = message.Objective, Type = GroupType.Private };
  • 5. public class Group { public Group( Guid id, string name, string objective, GroupType type) { //... } } //Usage var @group = new Group( message.GroupId, message.Name, message.Objective, GroupType.Private );
  • 6. public class PrivateGroup { private PrivateGroup(/* ... */) { /* ... */ } public static PrivateGroup Start( GroupId id, GroupName name, GroupObjective objective) { //... } } //Usage var @group = PrivateGroup.Start( new GroupId(message.GroupId), new GroupName(message.Name), GroupObjective.InferFrom(message.Objective) );
  • 7. public class Group { public Group( Guid id, string name, string objective, GroupType type) { if(id == Guid.Empty) throw new ArgumentException("...", "id"); if(string.IsNullOrEmpty(name)) throw new ArgumentException("...", "name"); if(name.Length > 255) throw new ArgumentException("...", "name"); if(objective != null && objective.Length == 0) throw new ArgumentException("...", "objective"); if(objective != null && objective.Length > 500) throw new ArgumentException("...", "objective"); if(type != GroupType.Private && type != GroupType.Public) throw new ArgumentException("...", "type"); //... } }
  • 8. public class PrivateGroup { public static PrivateGroup Start( GroupId id, GroupName name, GroupObjective objective) { //... } }
  • 9. public class GroupId { private readonly Guid value; public GroupId(Guid value) { if(value == Guid.Empty) throw new ArgumentException("...", "value"); this.value = value; } public override bool Equals(object other) { if(other == null || other.GetType() != this.GetType()) return false; return ((GroupId)other).value.Equals(this.value); } public override int GetHashCode() { return this.value.GetHashCode(); } //... }
  • 10. public class GroupName { private readonly string value; public GroupName(string value) { if(string.IsNullOrEmpty(value)) throw new ArgumentException("...", "value"); if(value.Length > Metadata.MaximumGroupNameLength) throw new ArgumentException("...", "value"); this.value = value; } //... } //Usage @group.Rename(new GroupName(message.Name));
  • 11. public class GroupObjective { private static readonly GroupObjective NotSpecified = new GroupObjective(null); private readonly string value; private GroupObjective(string value) { this.value = value; } public static GroupObjective InferFrom(string value) { if(value == null) return NotSpecified; if(value.Length == 0) throw new ArgumentException("...", "value"); if(value.Length > Metadata.MaximumGroupObjectiveLength) throw new ArgumentException("...", "value"); return new GroupObjective(value); } }
  • 12. public class GroupObjective { //... public static GroupObjective InferFromMarkdown(string value) { if(value == null) return NotSpecified; MarkDownDocument parsedDocument; if(!MarkDownParser.TryParse(value, out parsedDocument)) throw new ArgumentException("...", "value"); var document = parsedDocument.Sanitize(); if(document.Text.Length == 0) throw new ArgumentException("...", "value"); if(document.Text.Length > Metadata.MaximumGroupObjectiveLength) throw new ArgumentException("...", "value"); return new GroupObjective(document.ToString()); } }
  • 13. public class GroupObjective { private static readonly GroupObjective NotSpecified = new GroupObjective(MimeType.PlainText, null); private readonly MimeType mimeType; private readonly string value; private GroupObjective(MimeType mimeType, string value) { this.mimeType = mimeType; this.value = value; } public static GroupObjective InferFromMarkdown(MarkDownDocument value) { if(value == null) return NotSpecified; var document = value.Sanitize(); if(document.Text.Length == 0) throw new ArgumentException("...", "value"); if(document.Text.Length > Metadata.MaximumGroupObjectiveLength) throw new ArgumentException("...", "value"); return new GroupObjective(MimeType.Markdown, document.ToString()); } }
  • 14. public class GroupObjective { public static GroupObjective InferFromMarkdown(SanitizedMarkDownDocument document) { if(value == null) return NotSpecified; if(document.Text.Length == 0) throw new ArgumentException("...", "value"); if(document.Text.Length > Metadata.MaximumGroupObjectiveLength) throw new ArgumentException("...", "value"); return new GroupObjective(MimeType.Markdown, document.ToString()); } }
  • 15.
  • 16.
  • 17. public class FillPercentage { private readonly double value; public FillPercentage(double value) { if(value < 0) throw new ArgumentOutOfRangeException("value", value, "The fill percentage must be greater than or equal to 0."); this.value = value; } public static FillPercentage FromPercentNotation(double value) { return new FillPercentage(value / 100d); } public static implicit operator double(FillPercentage instance) { return instance.value; } }
  • 18. public class MaximumFillPercentage { private readonly double value; public MaximumFillPercentage(double value) { if(value < 1) throw new ArgumentOutOfRangeException("value", value, "The maximum fill percentage must be greater than or equal to 1."); this.value = value; } public static MaximumFillPercentage FromPercentNotation(double value) { return new MaximumFillPercentage(value / 100d); } public static implicit operator double(MaximumFillPercentage instance) { return instance.value; } }
  • 19. public class MaximumFillPercentage : IComparable<FillPercentage> { //... public int CompareTo(FillPercentage other) { return this.value.Compare(other); } }
  • 20. public class MaximumFillPercentage : IComparable<FillPercentage> { //... public static bool operator < (MaximumFillPercentage left, FillPercentage right) { return left.CompareTo(right) == -1; } public static bool operator <= (MaximumFillPercentage left, FillPercentage right) { return left.CompareTo(right) <= 0; } public static bool operator > (MaximumFillPercentage left, FillPercentage right) { return left.CompareTo(right) == 1; } public static bool operator >= (MaximumFillPercentage left, FillPercentage right) { return left.CompareTo(right) >= 0; } }
  • 21. public class FillPercentage { //... public bool Exceeds(MaximumFillPercentage maximum) { return maximum < this; } }
  • 22. public class Session { // ... public Session Add(Timeslot timeslot) { return new Session(this.StartTime, this.EndTime, this.MaximumFillPercentage, this.Timeslots.Add(timeslot)); } public MaximumFillPercentage MaximumFillPercentage { get; } public FillPercentage CurrentFillPercentage { get { var sessionDuration = this.EndTime.Subtract(this.StartTime); var totalTimeslotDuration = this.Timeslots.Aggregate(Duration.Zero, (current, timeslot) => current.Add(timeslot.DurationBetween(this.StartTime, this.EndTime))) return new FillPercentage( totalTimeslotDuration / sessionDuration); } } }
  • 23. public class ScheduleDay { public void Book(AppointmentTimeslot timeslot) { var beforeBooking = FindSessionToBookOn(timeslot); var afterBooking = beforeBooking.Add(timeslot); if (afterBooking .CurrentFillPercentage .Exceeds(beforeBooking.MaximumFillPercentage)) { throw new MaximumFillPercentageExceededException(/* ... */); } // ... } }
  • 26.
  • 27.
  • 28. public class PrivateGroupBehavior : BehaviorModule { public PrivateGroupBehavior( ITenantRepository tenantRepository, IPrivateGroupRepository groupRepository) { Receive<StartPrivateGroup>(async message => { var tenant = await tenantRepository.Get(new TenantId(message.TenantId)); var @group = tenant.StartPrivateGroup( new GroupId(message.GroupId), new GroupName(message.GroupName); GroupObjective.InferFromMarkdown(message.GroupObjective)); groupRepository.Add(@group); }); } }
  • 29. public class PrivateGroupScenarios { [Fact] public async Task when_starting_a_private_group() { using(var context = new GroupContext()) { //Arrange context.Tenants.Add(new TenantData { TenantId = 123, Subdomain = "nespresso" }); var module = new PrivateGroupBehavior( new TenantRepository(context), new PrivateGroupRepository(context) ); //Act await module.Send(new StartPrivateGroup { TenantId = 123, GroupId = new Guid("a9a620ca-c8c2-41b7-b7c0-fc03881ed00d"), Name = "Prototype Retail Machines" }); //Assert Assert.Equal(1, context.PrivateGroups.Count()); GroupData actual = context.PrivateGroups.Single(); Assert.Equal(123, actual.TenantId); Assert.Equal(new Guid("a9a620ca-c8c2-41b7-b7c0-fc03881ed00d"), actual.GroupId); Assert.Equal("Prototype Retail Machines", actual.Name); } } }
  • 30. public class PrivateGroupScenarios { [Fact] public Task when_starting_a_private_group() { var tenantId = new Random().Next(1, 100); var groupId = Guid.NewGuid(); return new Scenario() .Given(tenantId, new TenantSnapshot { TenantId = tenantId, Subdomain = "nespresso" }) .When(new StartPrivateGroup { TenantId = tenantId, GroupId = groupId, Name = "Prototype Retail Machines" }) .Then(groupId, new PrivateGroupSnapshot { TenantId = tenantId, GroupId = groupId, Name = "Prototype Retail Machines" }) .Assert(); // <- the boring stuff happens here } //example of testing a state-based model }
  • 31. public class PrivateGroupScenarios { [Fact] public Task when_starting_a_private_group() { var tenantId = new Random().Next(1, 100); var groupId = Guid.NewGuid(); return new Scenario() .Given(tenantId, new RegisteredAsTenant { TenantId = tenantId, Subdomain = "nespresso" }) .When(new StartPrivateGroup { TenantId = tenantId, GroupId = groupId, Name = "Prototype Retail Machines" }) .Then(groupId, new PrivateGroupStarted { TenantId = tenantId, GroupId = groupId, Name = "Prototype Retail Machines" }) .Assert(); // <- the boring stuff happens here } //example of testing a event-driven model }
  • 32. public class PrivateGroupScenarios { [Fact] public Task when_starting_a_private_group() { var tenantId = new TenantId(new Random().Next(1, 100)); var subdomain = new Subdomain("nespresso"); var groupId = new GroupId(Guid.NewGuid()); var groupName = new GroupName("Prototype Retail Machines"); return new Scenario() .Given(tenantId, new RegisteredAsTenant { TenantId = tenantId, Subdomain = subdomain }) .When(new StartPrivateGroup { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Then(groupId, new PrivateGroupStarted { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Assert(); } //introducing value objects in your tests }
  • 33. public class PrivateGroupScenarios { [Fact] public Task when_starting_a_private_group() { var tenantId = new TenantId(new Random().Next(1, 100)); var subdomain = new Subdomain("nespresso"); var groupId = new GroupId(Guid.NewGuid()); var groupName = new GroupName("Prototype Retail Machines"); return new Scenario() .Given(tenantId, new RegisteredAsTenant { TenantId = tenantId, Subdomain = subdomain }) .When(new StartPrivateGroup { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Then(groupId, new PrivateGroupStarted { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Assert(); } //introducing value objects in your tests }
  • 34. public class PrivateGroupScenarios { [Fact] public Task when_starting_a_private_group() { var fixture = new ScenarioFixture(); var tenantId = fixture.Create<TenantId>(); var groupId = fixture.Create<GroupId>(); var groupName = fixture.Create<GroupName>(); return new Scenario() .Given(tenantId, new RegisteredAsTenant { TenantId = tenantId, Subdomain = fixture.Create<Subdomain>() }) .When(new StartPrivateGroup { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Then(groupId, new PrivateGroupStarted { TenantId = tenantId, GroupId = groupId, Name = groupName }) .Assert(); } //generate anonymous, random test values }
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51. public class MembershipRequest { public void Approve(PersonId approver) { if (this.Archived) throw new MembershipRequestWasArchived(this.GroupId, this.RequestId); if (this.Declined) throw new MembershipRequestWasDeclined(this.GroupId, this.RequestId); if (this.Cancelled) throw new MembershipRequestWasCancelled(this.GroupId, this.RequestId); if (this.Approved) return; // ... happy path ... } }
  • 52. public class MembershipRequest { private enum State { Requested, Approved, Declined, Cancelled, Archived } public void Approve(PersonId approver) { if (this.CurrentState == State.Archived) throw new MembershipRequestWasArchived(this.GroupId, this.RequestId); if (this.CurrentState == State.Declined) throw new MembershipRequestWasDeclined(this.GroupId, this.RequestId); if (this.CurrentState == State.Cancelled) throw new MembershipRequestWasCancelled(this.GroupId, this.RequestId); if (this.CurrentState == State.Approved) return; // ... happy path ... this.CurrentState = State.Approved; } }
  • 53. public class MembershipRequest { private enum State { Initial, Requested, Approved, Declined, Cancelled, Archived } private enum Trigger { Request, Approve, Decline, Cancel, Archive } public void Approve(PersonId approver) { this.Machine.Fire(Trigger.Approve); // <- Magic // ... happy path ... } }
  • 54. public class MembershipRequest { public MembershipRequest() { this.Machine = ConfigureStatemachine(); } private StateMachine<State, Trigger> ConfigureStatemachine() { return new StateMachine<State, Trigger>(State.Initial) .Configure(State.Initial).Permit(Trigger.Request, State.Requested) .Configure(State.Requested) .Permit(Trigger.Approve, State.Approved) .Permit(Trigger.Decline, State.Declined) .Permit(Trigger.Cancel, State.Cancelled) .Permit(Trigger.Archive, State.Archived) .Configure(State.Approved).Ignore(Trigger.Approve) .GuardAnyOf(Trigger.Cancel, Trigger.Decline, Trigger.Archive, new MembershipRequestWasApproved(this.GroupId, this.RequestId)) .Configure(State.Cancelled).Ignore(Trigger.Cancel) .GuardAnyOf(Trigger.Approve, Trigger.Decline, Trigger.Archive, new MembershipRequestWasCancelled(this.GroupId, this.RequestId)) .Configure(State.Declined).Ignore(Trigger.Decline) .GuardAnyOf(Trigger.Approve, Trigger.Cancel, Trigger.Archive, new MembershipRequestWasDeclined(this.GroupId, this.RequestId)) .Configure(State.Archived).Ignore(Trigger.Archive) .GuardAnyOf(Trigger.Approve, Trigger.Cancel, Trigger.Decline, new MembershipRequestWasArchived(this.GroupId, this.RequestId)); } }