Professional Documents
Culture Documents
1 www.oscommerce.com
2 www.oscommerce.com/solutions/documentation
Figure 16.1 shows the main concepts in the osCommerce domain. More
details will be given later on.
The products in the store are manufactured by manufacturers, are
grouped into categories, and belong to tax classes.
Products may have attributes. An attribute is an option/value pair, such
as Size/Small, Size/Large, or Color/Yellow. The price of a product varies
depending on the attributes it has.
A customer has one or more addresses. Each address is located in a
country. Moreover, if the country has zones (states or provinces), then the
address must be located in a zone.
A shopping cart contains one or more items (not shown in the figure),
each of which is a quantity of a product with a set of attributes. When a
customer confirms that he wants to buy the contents of his shopping cart,
the system generates an order. An order contains one or more order lines,
each of which is a quantity of a product with a set of attributes.
In the following sections, we refine the above concepts and develop sev-
eral fragments of the structural and behavioral schemas of osCommerce.
Figure 16.2 shows the store data used by osCommerce. Store is a constant
entity type that has only one instance, which is created and initialized on
installation. We can ensure that there will be only one instance of Store
with the constraint defined by the following class operation:3
context Store::alwaysOneInstance():Boolean
body: Store.allInstances()->size() = 1
3 For the sake of uniformity, we define all constraints and derivation rules by op-
erations in this chapter.
Category
*
Manufacturer Option
0..1 *
*
*
* * Attribute
Product
* 1
0..1
*
TaxClass Product
Attribute Value
*
*
OrderLine *
1..*
1
* 1
Order Customer
*
ShoppingCart 1..*
Zone Address
0..1 *
* *
1
1
Country
Fig. 16.1. Main entity and relationship types in the osCommerce domain
Store
{«constant»} Country
0..1 1
name:String [0..1] name:String
owner:String [0..1]
1
eMailAddress:EMail [0..1]
eMailFrom:EMail [0..1]
expectedSortOrder:SortOrder *
expectedSortField:SortField
sendExtraOrderEMail:NameEMail [*] 0..1 0..1 Zone
displayCartAfterAddingProduct:Boolean
name:String
allowGuestToTellAFriend:Boolean
defaultSearchOperator:Operator
storeAddressAndPhone:String [0..1]
showCategoryCounts:Boolean «dataType»
taxDecimalPlaces:Natural EMail
displayPricesWithTax:Boolean
Fig. 16.2. Fragment of the structural schema dealing with a store and its
localization
To change the initial values, the system administrator starts the use case
Change store data:4
0..1 1 Store
DomainEvent StoreEvent
/myStore
NameChange ZoneChange
0..1 1
newName:String [0..1] newZone
Zone
effect()
effect()
[→ ExpectedSortOrderChange]
[→ ExpectedSortFieldChange]
[→ SendExtraOrderEMailChange]
[→ DisplayCartAfterAddingProductChange]
[→ AllowGuestToTellAFriendChange]
[→ DefaultSearchOperatorChange]
[→ AddressAndPhoneChange]
[→ ShowCategoryCountsChange]
[→ TaxDecimalPlacesChange]
[→ DisplayPricesWithTaxChange]
3. The system validates that the value is correct.
4. The system saves the new value.
5. The system displays the new values of the store data.
The system administrator repeats steps 2–5 until he is done.
We show only the definitions of the first and third domain events (Fig.
16.3); the others are quite similar. Since these event types change attributes
or associations of the store, it is practical to define the association with
Store in a single place (StoreEvent). In this case, the association is derived.
Given that Store has only one instance, the derivation rule is
context StoreEvent::myStore():Store
body: Store.allInstances()->any(true)
«utility»
MinimumValues
firstName:PositiveInteger
lastName:PositiveInteger
dateOfBirth:Boolean
password:PositiveInteger
companyName:Natural
streetAddress:Natural
postCode:PositiveInteger
city:Natural
phone:Natural
Fig. 16.4. Fragment of the structural schema dealing with the minimum values of
some attributes
DomainEvent
FirstNameMinimumChange
newMinimum:PositiveInteger
effect()
16.3.1 Manufacturers
Manufacturer
Language
name:String 1..*
*
imagePath:String [0..1] name:String
/added:DateTime {«constant»} code:String
lastModified:DateTime [0..1]
Manufacturer
InLanguage «dataType»
url:URL URL
urlClicked:Natural
lastClick:DateTime [0..1]
records the number of times a URL in a language has been clicked and the
last time that it was clicked.5
There are five constraints related to the fragment shown in Fig. 16.6.
The first three are that a Manufacturer is identified by its name, and that a
Language is identified by its name and by its code. Using class operations,
these constraints can be formally defined as follows:
context Manufacturer::nameIsUnique():Boolean
body: Manufacturer.allInstances()->isUnique(name)
context Language::nameIsUnique():Boolean
body: Language.allInstances()->isUnique(name)
context Language::codeIsUnique():Boolean
body: Language.allInstances()->isUnique(code)
osCommerce can only work properly if there is at least one language.
We can define this constraint with the following class operation:
context Language::atLeastOneLanguage():Boolean
body: Language.allInstances()->size() > 0
The last constraint is the rule that each manufacturer must have a URL
in each language. This can be defined with the operation
context Manufacturer::aURLinEachLanguage():Boolean
body:
self.language->size() = Language.allInstances()->size()
The attribute Manufacturer::added is constant and derived. Its deriva-
tion rule is
context Manufacturer::added():DateTime
body: Now()
Language
* Manufacturer
HasURL «dataType»
1
url
1 URL
*
*
Existing
Manufacturer
DomainEvent Manufacturer
URLEvent
Event
16.3.2 Categories
6 The navigation of n-ary associations assumes that they have been reified. In the
example of Fig. 16.7, it is assumed that there exists an association class named
HasURL.
String
0..1 parent name 1
Category
* imagePath:String [0..1] Language
0..1 *
child sortOrder:Natural name:String
/added:DateTime{«constant»} code:String
lastModified:DateTime [0..1] HasName
Fig. 16.8. Fragment of the structural schema dealing with product categories
DomainEvent
Language
*
NewCategory HasNew
imagePath:String[0..1]
Name
parent
Category
0..1 * sortOrder:Natural *
effect()
1 name
String
context NewCategory::effect()
post:
c.oclIsNew() and
c.oclIsTypeOf(Category) and
c.imagePath = self.imagePath and
c.sortOrder = self.sortOrder and
c.parent = self.parent and
Language.allInstances()->
forAll(l|
self.hasNewName->select(language=l).name =
c.hasName->select(language=l).name)
16.3.3 Products
Product
* 0..1
imagePath:String [0..1]
Manufacturer
model:String [0..1]
status:ProductStatus
price:Money * *
Category
quantityOnHand:Integer
quantityOrdered:Natural
/added:DateTime {«constant»} 1..*
*
lastModified:DateTime [0..1] Language
available:DateTime [0..1]
ProductInLanguage
«enumeration»
ProductStatus name:String
inStock description:String [0..1]
outOfStock url:URL [0..1]
viewed:Natural
«enumeration» HasOptionName
Sign 0..1
plus Option
minus
*
1 name *
Fig. 16.11. Fragment of the structural schema dealing with product attributes
store with the sizes Large and Small (i.e. there are two instances of Pro-
ductAttibute, one between FashionT-Shirt and the attribute Size/Large, and
the other between FashionT-Shirt and the attribute Size/Small).
The identification of Option and Value is similar to that of Product and
Category: in a given language, names are unique. The cardinalities of the
association HasOptionName ensure that each option has a unique name in
a language. Taken together with the rule that there is at least one language,
this guarantees the identifiability of options. Similar considerations apply
to values.
The entity types Attribute and ProductAttribute are identifiable by
means of a compound reference consisting of the two intrinsic relationship
types of their reification.
There are several use cases related to products. In what follows, we de-
scribe only the use case Add a product, which includes the use cases Add
product option and Add product option value.
DomainEvent
NewProductAttribute 1 Option
1 increment:Money
Product sign:Sign
1
effect() Value
16.4 Customers
«dataType»
Customer Address
gender:Gender [0..1] gender:Gender [0..1]
firstName:String firstName:String
lastName:String * 1..* lastName:String
dateOfBirth:Date [0..1] {ordered} company:String [0..1]
eMailAddress:EMail street:String [0..1]
phone:String [0..1] suburb:String [0..1]
fax:String [0..1] * 1 postCode:String
newsletter:Boolean [0..1] primary
city:String [0..1]
{subsets address}
password:String state:String [0..1]
lastLogon:DateTime [0..1]
numberLogons:Natural {«constant»} * {«constant»} *
/added:DateTime{«constant»}
0..1
lastModified:DateTime [0..1]
Zone
«enumeration»
Gender * 1
male 1
female
Country
Fig. 16.13. Fragment of the structural schema dealing with customers and
addresses
The second constraint ensures that the password is the same as the pass-
word confirmation:
context NewCustomer::passwordCorrect():Boolean
body: password = pwConfirmation
DomainEvent
NewCustomer
primary:Address
dateOfBirth:Date [0..1]
eMailAddress:EMail
phone:String [0..1]
fax:String [0..1]
newsletter:Boolean [0..1]
password:String
pwConfirmation:String
effect()
context NewCustomer::dobRight():Boolean
body:
MinimumValues.dateOfBirth implies dateOfBirth->notEmpty()
context NewCustomer::companyRight():Boolean
body:
MinimumValues.companyName > 0 implies
primary.company->notEmpty() and
primary.company.size()>= MinimumValues.companyName
context NewCustomer::streetRight():Boolean
body:
MinimumValues.streetAddress > 0 implies
primary.street->notEmpty() and
primary.street.size() >= MinimumValues.streetAddress
context NewCustomer::postCodeRight():Boolean
body: primary.postCode.size()>= MinimumValues.postCode
context NewCustomer::cityRight():Boolean
body:
MinimumValues.city > 0 implies
primary.city->notEmpty() and
primary.city.size() >= MinimumValues.city
context NewCustomer::phoneRight():Boolean
body:
MinimumValues.phone > 0 implies
phone->notEmpty() and
phone.size() >= MinimumValues.phone
context NewCustomer::effect()
post:
c.oclIsNew() and
c.oclIsTypeOf(Customer) and
c.gender = primary.gender and
c.firstName = primary.firstName and
c.lastName = primary.lastName and
c.dateOfBirth = dateOfBirth and
c.eMailAddress = eMailAddress and
c.phone = phone and
c.fax = fax and
c.newsletter = newsletter and
c.password = password and
c.numberLogons = 0 and
c.primary = primary and
c.address = Set{primary}
The online catalog includes the use cases that may be started by the cus-
tomers of the store. In the following, we describe the most important use
case (Place an order) and a query.
Shopping
CartItem
1
0..1 0..1 {«constant»} quantity:
Session ShoppingCart PositiveInteger
1..*
{ordered} /added:Date
0..1 1 {redefines
session} {«constant»}
* *
{disjoint,complete} {«constant»} {«constant»}
Product
Anonymous Customer
ShoppingCart ShoppingCart *
0..1 {«constant»} 0..1
1 Attribute
Customer
Fig. 16.15. Fragment of the structural schema dealing with shopping carts
context ShoppingCartItem::productHasTheAttributes():Boolean
body: product.attribute->includesAll(attribute)
context ShoppingCartItem::onlyOneAttributePerOption():Boolean
body: self.attribute->isUnique(option)
The creation and updating of a shopping cart are part of the use case
Place an order, which we shall describe in the next section. However, we
shall define here two main domain event types and one action request type,
shown in Fig. 16.16.
The domain event type RemoveProduct removes a shopping-cart item
from its shopping cart. Its effect is
context RemoveProduct::effect()
post: not shoppingCartItem@pre.oclIsKindOf(OclAny)
This postcondition ensures that the removed shopping-cart item will not
exist in the new state of the information base (i.e. after the execution of the
effect operation). Moreover, if the shopping cart has only one item, the
shopping cart will be removed as well (why?).
The domain event type ChangeQuantity changes the quantity of a shop-
ping-cart item. Formally, its effect is
context ChangeQuantity::effect()
post: shoppingCartItem.quantity = self.quantity
DomainEvent
ActionRequest
Remove
«dataType»
Product
«create»
LineChange Update
effect()
1..* ShoppingCart
remove:Boolean {ordered}
quantity: effect() Change
«create»
PositiveInteger Quantity
quantity:
PositiveInteger
effect()
1 1
Session ShoppingCart
1 1
Shopping
CartItem
Fig. 16.16. Definition of the action request type UpdateShoppingCart and the
domain event types RemoveProduct and ChangeQuantity
The effect is
context UpdateShoppingCart::effect()
post:
lineChange->forAll
(lc|let cartItem:ShoppingCartItem =
shoppingCart.shoppingCartItem->
at(lineChange->indexOf(lc))
in
(lc.remove or lc.quantity <> cartItem.quantity)
implies
if lc.remove then
rp.oclIsNew() and
rp.oclIsTypeOf(RemoveProduct) and
rp.shoppingCartItem = cartItem
else
cq.oclIsNew() and
cq.oclIsTypeOf(ChangeQuantity) and
cq.shoppingCartItem = cartItem and
cq.quantity = quantity
endif))
16.5.2 Orders
When the customer confirms that he wants to buy the contents of his shop-
ping cart, the system generates an order. Figure 16.17 shows the schema
fragment corresponding to orders.
Orders are identified by their attribute id, which is assigned automati-
cally when the order is created. The derivation rule is
context Order::id():PositiveInteger
body:
if Order.allInstances()->size() = 0 then 0
else
Order.allInstances()->
sortedBy(id)->last().id + 1
endif
Each order has a status. Initially the status is pending, but it may change
later on. Each change is represented by an instance of OrderStatusChange.
The derived attribute Order::status gives the current status of the order.
The derivation rule is
context Order::status():OrderStatus
body: orderStatusChange->sortedBy(added)->last()
The primary address of an order is that of its customer when the order is
created. This is captured by a creation-time derivation rule with the defin-
ing operation
context Order::primary():Address
body: customer.primary
Customer Product
1 1
* {«constant»} * {«constant»}
OrderLine
Order
{«constant»}
1
/id:PositiveInteger{«constant»} {«constant»} /name:String
/primary:Address {«constant»} 1..* /model:String [0..1]
billing:Address {«constant»}
{ordered} /price:Money
delivery:Address {«constant»} /finalPrice:Money
/eMailAddress:EMail {«constant»} quantity:PositiveInteger
/phone:String [0..1] {«constant»} 1
/purchased:DateTime {«constant»} {«constant»}
/lastModified:DateTime [0..1]
/status:OrderStatus *
/total:Money {«constant»} OrderLine
1 Attribute
{«constant»}
1..* {«constant»}
/option:String
OrderStatus /value:String
Change «enumeration» /increment:Money
{«constant»} OrderStatus /sign:Sign
status:OrderStatus pending * {«constant»}
/added:DateTime processing
comments:String delivered 1
Attribute
The final price of an order line is the price of the product plus or minus
the increments of its attributes. The derivation rule is
context OrderLine::finalPrice():Money
body:
if orderLineAttribute->isEmpty() then price
else
orderLineAttribute->collect
(if sign = Sign::plus then increment
else –increment
endif)->sum() + price
endif
DomainEvent
OrderConfirmation
Customer 1 delivery:Address
ShoppingCart shopping billing:Address
Cart comments:String
effect()
Query
Show
PreviousOrders
1 *
Customer answer
effect()
Set(TupleType(date:Date,
id:Natural,
name:String,
country:String,
status:OrderStatus,
total:Money))
The answer is given by the effect operation
context ShowPreviousOrders::effect()
post:
answer =
customer.order->collect(o|
Tuple{date = o.purchased.date,
id = o.id,
name = o.delivery.firstName.
concat(o.delivery.lastName),
country = o.delivery.country.name,
status = o.status,
total = o.total})->asSet()