Skip to content

Commit

Permalink
Implement complete booking procedure as single page js app
Browse files Browse the repository at this point in the history
- New resource RouteCandidate
- Add endpoint to update CargoRouting
- Expand UI functionality
- Adapt book new cargo feature test
  • Loading branch information
codeliner committed Apr 7, 2014
1 parent 83214fb commit 0ce0cf5
Show file tree
Hide file tree
Showing 17 changed files with 568 additions and 242 deletions.
38 changes: 7 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ the [features folder](https://github.com/codeliner/php-ddd-cargo-sample/tree/mas
We make use of [Behat](http://behat.org/) and [Mink](http://mink.behat.org/) to test our
business expectations.

You can run the feature tests by navigating to the project root and start the selenium server shipped with the sample app with following command:
`java -jar bin/selenium-server-standalone-2.37.0.jar`
After the server started successful open another console, navigate to project root again and run Behat with the command `php bin/behat`.

*If it does not work, check that the behat file is executable.

Unit Tests
----------
Unit Tests are of course also available. You can find them in [module/CargoBackend/tests](https://github.com/codeliner/php-ddd-cargo-sample/tree/master/module/CargoBackend/tests).
Expand All @@ -62,34 +68,4 @@ Maybe I've missed a concept that you hoped to find in the example.
Chapter Overview
----------------

###ChapterOne
`git checkout ChapterOne`

Chapter One release contains the first draft of the Cargo DDD model.
It contains the Entities `Cargo` and `Voyage` and also an `Application BookingService` that works with an `overbooking policy`
to allow the booking of a Cargo even when the Voyage has not enough free capacity.

[ChapterOne Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterOne-Review.md)
###ChapterTwo
`git checkout ChapterTwo`

In Chapter Two we learn the importance of the Ubiquitous Language. With it's help the team works out a Cargo Router and redefines the use cases for the Shipping Application. The `Application BookingService` is replaced with a `Application RoutingService`, cause the system focuses on planing an `Itinerary` for a `Cargo` that satisfies a `RouteSpecification`.

[ChapterTwo Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterTwo-Review.md)

###ChapterThree
`git checkout ChapterThree`

ChapterThree is about Model-Driven Design.

> "Design a portion of the software system to reflect the domain model in a very literal way, so that
> mapping is obvious. Revisit the model and modify it to be implemented more naturally in software,
> even as you seek to make it reflect deeper insight into the domain. Demand a single model that
> serves both purposes well, in addition to supporting a robust UBIQUITOUS LANGUAGE.
> Draw from the model the terminology used in the design and the basic assignment of responsibilities.
> The code becomes an expression of the model, so a change to the code may be a change to the
> model. Its effect must ripple through the rest of the project's activities accordingly."
>
> -- Eric Evans: Domain-Driven Design: Tackling Complexity in the Heart of Software
[ChapterThree Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterThree-Review.md)
The chapter overview has moved to the [PHP DDD Cargo Sample project page](http://codeliner.github.io/php-ddd-cargo-sample/)
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"keywords": [
"DDD",
"zf2",
"Domain Driven Design"
"doctrine",
"domain driven design",
"sample",
"REST"
],
"homepage": "https://github.com/codeliner/php-ddd-cargo-sample",
"authors": [
Expand Down
20 changes: 12 additions & 8 deletions features/book_new_cargo.feature
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
Feature: Book new Cargo
In order to manage a transport of a Cargo
As a booking clerk
I need to define an origin and a destination of a Cargo and assign it to a proper itinerary
I need to define an origin and a destination of a Cargo and assign it to a proper route

@javascript
Scenario: Add a Cargo and assign Itinerary
Given I am on "application/cargo/add"
When I select "DEHAM" from "origin"
And I select "USNYC" from "destination"
Scenario: Add a Cargo and assign route
Given I am on "application/bookingApp/index"
Then I should wait until I see "#book-cargo"
When I follow "book-cargo"
And I select "DEHAM" from "origin"
And I select "USNYC" from "final_destination"
And I click the submit button
And I follow "assign-itinerary-link-1"
Then the url should match "application/cargo/show/trackingid/[\w-]{36,36}"
And I should see 1 ".itinerary" elements
Then I should wait until I see "#route-candidate-list"
When I follow first ".assign-cargo-btn" link
Then I should wait until I see "#cargo-list"
When I click on first item in the list "#cargo-list"
Then I should see 1 ".itinerary" elements
30 changes: 27 additions & 3 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ static public function iniializeZendFramework()
public static function clearDatabase()
{
$em = self::$zendApp->getServiceManager()->get('doctrine.entitymanager.orm_default');
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\Cargo');
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\Cargo');
$q->execute();
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\RouteSpecification');
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\RouteSpecification');
$q->execute();
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\Itinerary');
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\Itinerary');
$q->execute();
}

Expand All @@ -80,6 +80,16 @@ public function iClickTheSubmitButton()
throw new \RuntimeException("Can not find the submit btn");
}
}

/**
* @Then /^I should wait until I see "([^"]*)"$/
*/
public function iShouldSeeAvailableRoutes($arg1)
{
$this->getSession()->wait(5000, '(0 === jQuery.active)');

$this->assertElementOnPage($arg1);
}

/**
* @When /^I click on first item in the list "([^"]*)"$/
Expand All @@ -95,6 +105,20 @@ public function iClickOnFirstItemInTheList($arg1)

$li->find('css', 'a')->click();
}

/**
* @When /^I follow first "([^"]*)" link$/
*/
public function iFollowFirstLink($arg1)
{
$page = $this->getSession()->getPage();

$link = $page->find('css', $arg1);

if ($link) {
$link->click();
}
}

/**
* @Given /^I wait until I am on page "(?P<page>[^"]+)"$/
Expand Down
32 changes: 29 additions & 3 deletions module/Application/config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@
),

),
'may_terminate' => true,
'child_routes' => array(
'routecandidates' => array(
'type' => 'Segment',
'options' => array(
'route' => '/routecandidates',
'defaults' => array(
'controller' => 'Api\Controller\RouteCandidates',
),
),
),
),
)
)
),
Expand All @@ -90,9 +102,10 @@
),
'service_manager' => array(
'factories' => array(
'main_navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
'cargo_form' => 'Application\Form\Service\CargoFormFactory',
'cargo_routing_resource' => 'Application\Resource\Service\CargoRoutingFactory',
'main_navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
'cargo_form' => 'Application\Form\Service\CargoFormFactory',
'cargo_routing_resource' => 'Application\Resource\Service\CargoRoutingFactory',
'route_candidate_resource' => 'Application\Resource\Service\RouteCandidateFactory'
),
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
Expand Down Expand Up @@ -178,6 +191,15 @@
'route_name' => 'api/cargoroutings',
'identifier_name' => 'tracking_id',
'collection_name' => 'cargoroutings',
'collection_http_options' => array('get', 'post'),
'resource_http_options' => array('get', 'put'),
),
'Api\Controller\RouteCandidates' => array(
'listener' => 'route_candidate_resource',
'route_name' => 'api/cargoroutings/routecandidates',
'collection_name' => 'routecandidates',
'collection_http_options' => array('get'),
'resource_http_options' => array(),
)
),
'renderer' => array(
Expand All @@ -188,6 +210,10 @@
'hydrator' => 'ArraySerializable',
'identifier_name' => 'tracking_id',
'route' => 'api/cargoroutings',
),
'CargoBackend\API\Booking\Dto\RouteCandidateDto' => array(
'hydrator' => 'ArraySerializable',
'route' => 'api/cargoroutings/routecandidates',
)
),
),
Expand Down
4 changes: 2 additions & 2 deletions module/Application/src/Application/Form/CargoForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function __construct(array $aLocationList, $anOptionList = array())
));

$this->add(array(
'name' => 'finalDestination',
'name' => 'final_destination',
'options' => array(
'label' => 'Destination',
'value_options' => $this->locations,
Expand Down Expand Up @@ -114,7 +114,7 @@ public function getInputFilter()
)
));

$destinationInput = new Input('finalDestination');
$destinationInput = new Input('final_destination');
$destinationInput->getValidatorChain()
->attach($inArrayValidator)
->attach($notSameValidator);
Expand Down
72 changes: 69 additions & 3 deletions module/Application/src/Application/Resource/CargoRouting.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
use Application\Form\CargoForm;
use CargoBackend\API\Booking\BookingServiceInterface;
use CargoBackend\API\Booking\Dto\CargoRoutingDto;
use CargoBackend\API\Booking\Dto\LegDto;
use CargoBackend\API\Booking\Dto\RouteCandidateDto;
use CargoBackend\API\Exception\CargoNotFoundException;
use PhlyRestfully\Exception\CreationException;
use PhlyRestfully\Exception\DomainException;
use PhlyRestfully\Exception\UpdateException;
use PhlyRestfully\ResourceEvent;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventInterface;
Expand Down Expand Up @@ -63,6 +66,7 @@ public function __construct(BookingServiceInterface $aBookingService, CargoForm
public function attach(EventManagerInterface $events)
{
$this->listeners[] = $events->attach('create', array($this, 'onCreate'));
$this->listeners[] = $events->attach('update', array($this, 'onUpdate'));
$this->listeners[] = $events->attach('fetch', array($this, 'onFetch'));
$this->listeners[] = $events->attach('fetchAll', array($this, 'onFetchAll'));

Expand All @@ -71,6 +75,11 @@ public function attach(EventManagerInterface $events)
$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'getIdFromResource', array($this, 'onGetIdFromResource'));
}

/**
* @param ResourceEvent $e
* @return CargoRoutingDto
* @throws \PhlyRestfully\Exception\CreationException
*/
public function onCreate(ResourceEvent $e)
{
$data = $e->getParam('data');
Expand All @@ -87,17 +96,22 @@ public function onCreate(ResourceEvent $e)

$trackingId = $this->bookingService->bookNewCargo(
$this->cargoFrom->get('origin')->getValue(),
$this->cargoFrom->get('finalDestination')->getValue()
$this->cargoFrom->get('final_destination')->getValue()
);

$cargoRouting = new CargoRoutingDto();
$cargoRouting->setTrackingId($trackingId);
$cargoRouting->setOrigin($this->cargoFrom->get('origin')->getValue());
$cargoRouting->setFinalDestination($this->cargoFrom->get('finalDestination')->getValue());
$cargoRouting->setFinalDestination($this->cargoFrom->get('final_destination')->getValue());

return $cargoRouting;
}

/**
* @param ResourceEvent $e
* @return CargoRoutingDto
* @throws \PhlyRestfully\Exception\DomainException
*/
public function onFetch(ResourceEvent $e)
{
$trackingId = $e->getRouteMatch()->getParam('tracking_id');
Expand All @@ -111,11 +125,43 @@ public function onFetch(ResourceEvent $e)
return $cargoRouting;
}

/**
* @param ResourceEvent $e
* @return \CargoBackend\API\Booking\Dto\CargoRoutingDto[]
*/
public function onFetchAll(ResourceEvent $e)
{
return $this->bookingService->listAllCargos();
}

/**
* @param ResourceEvent $e
* @return \CargoBackend\API\Booking\Dto\CargoRoutingDto
* @throws \PhlyRestfully\Exception\UpdateException
*/
public function onUpdate(ResourceEvent $e)
{
$trackingId = $e->getRouteMatch()->getParam('tracking_id');

$data = $e->getParam('data');

if (! isset($data->legs)) {
throw new UpdateException("Legs missing in CargoRouting payload", 400);
}

$routeCandidate = new RouteCandidateDto();

$routeCandidate->setLegs($this->toLegDtosFromData($data->legs));

$this->bookingService->assignCargoToRoute($trackingId, $routeCandidate);

return $this->bookingService->loadCargoForRouting($trackingId);
}

/**
* @param EventInterface $e
* @return bool|string
*/
public function onGetIdFromResource(EventInterface $e)
{
$resource = $e->getParam('resource');
Expand All @@ -127,6 +173,26 @@ public function onGetIdFromResource(EventInterface $e)
return false;
}

//@TODO: Implement methods
/**
* @param array $legs
* @return LegDto[]
*/
private function toLegDtosFromData(array $legs)
{
$legDtos = array();

foreach ($legs as $legData) {
$legDto = new LegDto();

$legDto->setLoadLocation($legData['load_location']);
$legDto->setUnloadLocation($legData['unload_location']);
$legDto->setLoadTime($legData['load_time']);
$legDto->setUnloadTime($legData['unload_time']);

$legDtos[] = $legDto;
}

return $legDtos;
}
}

Loading

0 comments on commit 0ce0cf5

Please sign in to comment.