1. Trang chủ
  2. » Công Nghệ Thông Tin

NET Domain-Driven Design with C#P roblem – Design – Solution phần 4 doc

43 382 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề NET Domain-Driven Design with C#
Trường học University of Sample
Chuyên ngành Software Engineering
Thể loại Lecture notes
Năm xuất bản 2008
Thành phố Unknown
Định dạng
Số trang 43
Dung lượng 329,14 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Class EntityBase Fields Methods Properties ActualCompletionDate Address AdjustedCompletionDate AdjustedConstructionCost AeChangeOrderAmount AgencyApplicationNumber AgencyFileNumber Allow

Trang 1

I promised earlier to show how I was going to deal with the Address Value objects in the XAML code, so

it is time to show it in the XAML:

ProjectInformationViewModel ; instead I am binding to the properties of the ProjectAddress

order to make sure that I am entering a valid address

I love the fact that I can bind to my ViewModel and get the type of functionality that I just showed in

these examples without having to write any procedural code in my View!

there is just too much There is nothing really special going on with the rest of it; it is the same pattern as

Trang 2

Summar y

The end result in the UI is not that spectacular, but I certainly covered a lot of ground in getting there In this chapter, I first defined and modeled all of the objects that make up what a Project is and then started analyzing the classes further in order to define the Aggregate Boundaries Once the Aggregate

Boundaries were defined, the Aggregate Roots were chosen, and then I defined what the various repositories were for the Aggregates After the repositories were designed and implemented, I then designed and implemented the ViewModel and the View for the use case scenario of editing Projects

I know it may not sound like much, but I actually wrote a lot of code and refactored a lot of code in the process of getting to where I am now The rest of the way should be well - paved for code reuse in the SmartCA application

Trang 4

Companies and Contacts

Last chapter I showed how both Companies and Contacts were part of the Project Aggregate Since the focus was on the Project Aggregate, not much was done with these two Entities This chapter,

I will dive in and take a deeper look at the Company and Contact Aggregates, and I will show how they relate to the Project Aggregate

The Problem

One of the problems with the legacy application is that it does not handle tracking Companies, Contacts, and their associated Addresses very well The current system does not allow multiple Addresses per Company or Contact, and as a result the users are often entering the same Companies and Contacts into the system as duplicate records in order to show a different Address for the Company or Contact

With that being said, it sounds like a database issue of having denormalized data It is not the point of this book to dwell on the database design; I believe that the focus of the problem needs to

be on the domain model If the domain model is designed properly, it can handle this problem

Remember, one of the tenets of Domain - Driven Design, which I discussed in Chapter 2 , is

persistence ignorance Therefore, the application ’ s data store could be a text file for all I care, because

it is abstracted away by the Repository Framework

The Design

In the SmartCA domain, the purpose of a Project is to bring together and manage all of the people involved in the construction process In the next few sections, I will be designing the domain model, determining the Project Aggregate and its boundaries, and designing the repository for Projects

Trang 5

Designing the Domain Model

Companies and Contacts are extremely important in the SmartCA domain, as they ensure that the right

construction documents get to the right people in a timely manner There is a slight distinction between a

Contact and a ProjectContact A ProjectContact is a Contact that happens to be part of a Project, whereas

a Contact may or may not be on a Project

Companies and Contacts both have multiple Addresses, but Companies also must have one of their

Addresses designated as their headquarters address Figure 4.1 is a diagram showing the relationships

between Companies, Contacts, ProjectContacts, and Addresses

HeadquartersAddressAddress

Project Contact

ContactCompany

Figure 4.1: Company and Contact Aggregates

Each contact belongs to a single Company, but people move around, and therefore Contacts often change

companies over the course of time You may notice that in this diagram that a ProjectContact contains a

Contact, but does not inherit from a Contact This is purely my preference to stick with the Gang - of - Four

advice by favoring composition over inheritance

The Gang - of - Four, also known as GoF, refers to the four authors who wrote the classic

software - engineering book Design Patterns: Elements of Reusable Object - Oriented Software

The book ’ s authors are Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides

Address Value class ProjectContact is not the root of an aggregate; it is an Entity, but it actually

belongs to the Project Aggregate (see Figure 4.2 )

Trang 6

Class EntityBase Fields

Methods

Properties ActualCompletionDate Address AdjustedCompletionDate AdjustedConstructionCost AeChangeOrderAmount AgencyApplicationNumber AgencyFileNumber Allowances ConstructionAdministrator Contacts

ContingencyAllowanceAmount ContractDate

ContractReason Contracts CurrentCompletionDate EstimatedCompletionDate EstimatedStartDate Name Number OriginalConstructionCost Owner

PercentComplete PrincipalInCharge Remarks Segment TestingAllowanceAmount TotalChangeOrderDays TotalChangeOrdersAmount TotalSquareFeet UtilityAllowanceAmount

Contact

Class Person Fields

Methods

Properties Email FaxNumber JobTitle MobilePhoneNumber PhoneNumber Remarks

Project Addresses

Addresses

Contact

Company

Class EntityBase Fields

Methods

Properties Abbreviation FaxNumber Name PhoneNumber Remarks Url

ProjectContact

Class EntityBase Fields

Methods

Properties OnFinalDistribution

Figure 4.2: Classes composing the Company and Contact Aggregates

The first way is to go to the Company Aggregate, and the second way is to go to the Contact Aggregate, navigate to a particular Contact, and then from the Contact navigate to a Company via the

CurrentCompany property

The third Aggregate in the figure is one I have already shown, the Project Aggregate This time around,

Aggregate and have defined its relationship with the Contact Aggregate to be one of composition

Trang 7

Class EntityBase Fields

Methods

Properties ActualCompletionDate Address AdjustedCompletionDate AdjustedConstructionCost AeChangeOrderAmount AgencyApplicationNumber AgencyFileNumber Allowances ConstructionAdministrator Contacts

ContingencyAllowanceAmount ContractDate

ContractReason Contracts CurrentCompletionDate EstimatedCompletionDate EstimatedStartDate Name Number OriginalConstructionCost Owner

PercentComplete PrincipalInCharge Remarks Segment TestingAllowanceAmount TotalChangeOrderDays TotalChangeOrdersAmount TotalSquareFeet UtilityAllowanceAmount

Contact

Class Person Fields

Methods

Properties Email FaxNumber JobTitle MobilePhoneNumber PhoneNumber Remarks

Project Addresses

ProjectContact

Class EntityBase Fields

Methods

Properties OnFinalDistribution

Figure 4.3: The Company and Contact Aggregate boundaries

Designing the Repositories

Following the one repository per Aggregate rule, there are three repositories to look at in this chapter, the

CompanyRepository , the ContactRepository , and last a revised ProjectRepository Figure 4.4

shows the company and contact repositories

I did not show the Project Aggregate Repository classes since they are still the same, they will just have

some new behavior added to them

The ICompanyRepository Interface

Trang 8

GenericAbstractClass RepositoryBase<T>

IContactRepository

Interface IRepository<Contact>

IRepository<T>

GenericInterface

ContactRepository

Class SqlCeRepositoryBase<Contact>

IContactRepository

SqlCeRepositoryBase<T>

GenericAbstractClass RepositoryBase<T>

ICompanyRepository

Interface IRepository<Company>

IRepository<T>

GenericInterface

CompanyRepository

Class SqlCeRepositoryBase<Company>

ICompanyRepository

Figure 4.4: The Company and Contact Aggregate Repositories

The IContactRepository Interface

using System;

using SmartCA.Infrastructure.RepositoryFramework;

namespace SmartCA.Model.Companies{

public interface IContactRepository : IRepository < Contact >

{ }}

The IProjectRepository Interface Revisited

using System;

using System.Collections.Generic;

using SmartCA.Infrastructure.RepositoryFramework;

namespace SmartCA.Model.Projects{

public interface IProjectRepository : IRepository < Project >

(continued)

Trang 9

{

IList < Project > FindBy(IList < MarketSegment > segments, bool completed);

Project FindBy(string projectNumber);

IList < MarketSegment > FindAllMarketSegments();

void SaveContact(ProjectContact contact);

}

}

ProjectContact is not an Aggregate Root and is part of the Project Aggregate means that I must

class is just an Entity in the Project Aggregate; it is not its own Aggregate Root

Writing the Unit Tests

Just as in the last chapter, before implementing the solution for managing Companies and Contacts, I am

first going to write some unit tests of what I expect of the Company and Contact Repository

implementations It is important to remember that these tests will compile correctly, but they will also

fail when run, and that is what I expect They will pass once I write the code for the Repository

implementations in the Solution section

The ICompanyRepository Unit Tests

going to go over the steps of creating this class, as I covered that in Chapter 3 I am not going to show how

IRepository < Company > interface, I am going to test the methods from the IRepository < Company >

interface

The FindByKeyTest Method

that match the given Market Segments and have not completed

// Set the Key value

object key = “8b6a05be-6106-45fb-b6cc-b03cfa5ab74b”;

(continued)

Trang 10

// Find the Company Company company = this.repository.FindBy(key);

// Verify the Company’s name Assert.AreEqual(“My Company”, company.Name);

} The method first starts out by initializing a unique identifier string value It then passes that value to the ICompanyRepository interface instance in order to retrieve a Company with that particular Key value Once the Company instance is returned from the repository, the Company ’ s name is validated

The FindAllTest Method

have been returned by the Company Repository:

}

of the repository to map the data correctly from the data store into Company instances Later in the

I messed up when the test fails

The AddTest Method

(continued)

Trang 11

// Commit the transaction

this.unitOfWork.Commit();

// Reload the Company and verify it’s name

Company savedCompany = this.repository.FindBy(company.Key);

Assert.AreEqual(“My Test Company”, savedCompany.Name);

to write the Company ’ s data to the data store

perform is to remove the Company Removing the Company that was just created leaves the data store

in the same state as it was in before the method started, which is important for the rest of the tests that

may depend on a known state of the data store Otherwise, some of the other tests may fail because there

was data in the data store that was not expected

The UpdateTest Method

then verify that the change was persisted properly:

// Set the Key value

object key = “59427e22-0c9e-4821-95d6-9c9f541bf37a”;

// Find the Company

Company company = this.repository.FindBy(key);

// Change the Company’s Name

company.Name = “My Updated Company”;

Trang 12

// Commit the transaction this.unitOfWork.Commit();

// Verify that the change was saved Company savedCompany = this.repository.FindBy(company.Key);

Assert.AreEqual(“My Updated Company”, savedCompany.Name);

}

interface to commit the transaction Last, I verify that the change actually made it to the data store by

I just assigned earlier in the method

The RemoveTest Method

// Remove the Company from the Repository this.repository.Remove(company);

// Commit the transaction this.unitOfWork.Commit();

// Verify that there is now one less Company in the data store IList < Company > companies = this.repository.FindAll();

Assert.AreEqual(1, companies.Count);

}

before to find a Company Once I have found the Company, I remove it from the repository After

I verify that the change actually made it to the data store by using the repository to find all of the Company instances and making sure that there is now one fewer Company than before

Trang 13

The IContactRepository Unit Tests

any new methods, I have decided not to show any of the unit test code for it Its test class will have all of

around will be a Contact instead of a Company

The IProjectRepository Unit Test

I will only unit test that method

// Try to get the Project

Project project = this.repository.FindBy(projectNumber);

// Get the old count of Project Contacts

int oldCount = project.Contacts.Count;

// Get a Contact

IContactRepository contactRepository =

RepositoryFactory.GetRepository < IContactRepository, Contact > ();

object contactKey = “cae9eb86-5a86-4965-9744-18326fd56a3b”;

Contact contact = contactRepository.FindBy(contactKey);

// Create a Project Contact

ProjectContact projectContact = new ProjectContact(project,

// Reload the the Project

Project updatedProject = this.repository.FindBy(“12345.00”);

// Verify that there is a new ProjectContact now

Assert.AreEqual(oldCount, updatedProject.Contacts.Count - 1);

}

Trang 14

The first part of the code should remind you of the test code from the last chapter, where I tested finding

ProjectContact instance, I then save it to the IProjectRepository instance and commit the

The Solution

The design is in place for the Company and Contact domain models, the Company and Contact Aggregates have been defined and their boundaries have been determined, and the repositories have been designed with their associated tests It is time to start the code implementation In this section, I will be implementing these designs, as well as implementing the ViewModel and the View for Companies and Contacts

The Company Class

Company class in Chapter 3 , but now I have added some more properties to it:

using System;

using SmartCA.Infrastructure.DomainBase;

using System.Collections.Generic;

namespace SmartCA.Model.Companies{

public class Company : EntityBase {

private string name;

private string abbreviation;

private Address headquartersAddress;

private List < Address > addresses;

private string phoneNumber;

private string faxNumber;

private string url;

private string remarks;

public Company() : this(null) {

} public Company(object key) : base(key)

(continued)

Trang 15

get { return this.name; }

set { this.name = value; }

}

public string Abbreviation

{

get { return this.abbreviation; }

set { this.abbreviation = value; }

get { return this.phoneNumber; }

set { this.phoneNumber = value; }

}

public string FaxNumber

(continued)

Trang 16

{ get { return this.faxNumber; } set { this.faxNumber = value; } }

public string Url {

get { return this.url; } set { this.url = value; } }

public string Remarks {

get { return this.remarks; } set { this.remarks = value; } }

}}

address is really just one of its addresses that has been deemed as a headquarters address In the setter

having two constructors, a default constructor and a parameterized constructor containing the Key value

exist in the data store

The Contact Class

public class Contact : Person {

private string jobTitle;

private string email;

private string phoneNumber;

private string mobilePhoneNumber;

private string faxNumber;

private string remarks;

(continued)

Trang 17

private Company currentCompany;

private IList < Address > addresses;

public Contact(object key)

: this(key, null, null)

{

}

public Contact(object key, string firstName, string lastName)

: base(key, firstName, lastName)

get { return this.jobTitle; }

set { this.jobTitle = value; }

}

public string Email

{

get { return this.email; }

set { this.email = value; }

}

public string PhoneNumber

{

get { return this.phoneNumber; }

set { this.phoneNumber = value; }

}

public string MobilePhoneNumber

{

get { return this.mobilePhoneNumber; }

set { this.mobilePhoneNumber = value; }

}

(continued)

Trang 18

public string FaxNumber {

get { return this.faxNumber; } set { this.faxNumber = value; } }

public string Remarks {

get { return this.remarks; } set { this.remarks = value; } }

public Company CurrentCompany {

get { return this.currentCompany; } set { this.currentCompany = value; } }

public IList < Address > Addresses {

get { return this.addresses; } }

}}

CurrentCompany property contains a reference to a Company instance The Company class, however,

The ProjectContact Class

using System;

using SmartCA.Model.Contacts;

using SmartCA.Infrastructure.DomainBase;

namespace SmartCA.Model.Projects{

public class ProjectContact : EntityBase {

private Project project;

private bool onFinalDistributionList;

private Contact contact;

public ProjectContact(Project project, object key, Contact contact) : base(key)

(continued)

Trang 19

get { return this.onFinalDistributionList; }

set { this.onFinalDistributionList = value; }

}

}

}

Contact in its constructor, as well as a Project instance and a Key value The property that

property This property is used to designate which Contacts in a Project are to receive copies of

documents for things like Submittal Transmittals, Change Orders, and so on once they become final The

ProjectContact class also maintains a reference to the Project to which it belongs via the Project

The Repository Implementations

In this section, I will be writing the code for the Company and Contact Repositories, as well as

refactoring part of the Project Repository

The Company Repository

Trang 20

{ } public CompanyRepository(IUnitOfWork unitOfWork) : base(unitOfWork)

{ } #endregion All Repository implementations will follow this same pattern

BuildChildCallbacks

SqlCeRepositoryBase < > , but it does make use of the Template Method pattern, and BuildChildCallbacks is one of those abstract template methods that the ProjectRepository must implement

#region BuildChildCallbacks

protected override void BuildChildCallbacks() {

this.ChildCallbacks.Add(“addresses”, delegate(Company company, object childKeyName) {

this.AppendAddresses(company);

});

} #endregion

The AppendAddresses Callback

method queries the CompanyAddress table to get the list of addresses for the Company:

private void AppendAddresses(Company company) {

string sql = string.Format (“SELECT * FROM CompanyAddress WHERE CompanyID = ‘{0}’”, company.Key);

using (IDataReader reader = this.ExecuteReader(sql))

(continued)

Trang 21

IDataReader instance using static field mappings It then asks the CompanyFactory class whether the

internal static bool IsHeadquartersAddress(IDataReader reader)

{

return DataHelper.GetBoolean(reader[FieldNames.IsHeadquarters]);

}

property of the Company instance

GetBaseQuery

GetBaseQueryMethod Here is the CompanyRepository class ’ s override of the abstract method:

This simply returns the SQL statement for the Company Aggregate Again, just as I mentioned before, by

abstracting this, the SqlCeRepositryBase < > class is able to pull in the two “ FindBy ” methods from

(continued)

Ngày đăng: 09/08/2014, 12:22

TỪ KHÓA LIÊN QUAN