Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Functional & Event
                             Driven
                        another approach to domain modeling




Thu...
Debasish Ghosh

                             @debasishg on Twitter

                                            code @
   ...
Agenda
                       • Domain Models
                       • Why Functional Domain Models
                      ...
Domain Modeling
                       • We limit ourselves strictly to how the
                         domain behaves in...
Why Functional ?

                       • Pure with localization of side-effects
                       • Ability to reas...
A Functional Domain
                               Model
                       • Built on (mostly) pure abstractions
    ...
Immutability

                       • Algebraic Data Types for representation of
                         domain entities...
Representation as
                            ADTs
     case class Trade(account: Account, instrument: Instrument,
       ...
Representation as
                            ADTs
         // various tax/fees to be paid when u do a trade
         seal...
Pure functions as
                       domain behaviors
    // get the list of tax/fees applicable for this trade
    //...
Pure functions as
                          domain behaviors
               // enrich a trade with tax/fees and compute ne...
Pure functions as
                       domain behaviors
   // combinator to value a tax/fee for a specific trade
   val ...
Composability

                       Secret sauce ? Functions compose. We can
                       pass functions as fir...
Updating a Domain
                        Structure functionally
                       • A Type-Lens is a data structure ...
A Type Lens in Scala
               // change ref no
               val refNoLens: Lens[Trade, String] =
                 ...
Lens under Composition
                       • What’s the big deal with a Type Lens ?
                        ✦   Lens co...
Lens under
                         composition
     Using the personStreetL lens we may access or
     set the (indirect)...
Functional updates using type
                                   lens
             // change ref no
             val refNo...
Managing States

                       • But domain objects don’t exist in isolation
                       • Need to int...
A day in the life of a
                             Trade object
                                                    tax/f...
What’s the big deal ?
       All these sound like changing states of a newly created
                           Trade obje...
• but ..
                       • Changing state through in-place mutation is
                         destructive

      ...
What if we would like to have our
                       system rolled back to THAT POINT IN
                             ...
We are just being lossy

Thursday 12 April 12
• The solution is to keep information in such a
                       way that we have EVERY BIT OF DETAILS
             ...
Event Sourcing
                       • Preserves the temporality of the data
                         structure
         ...
Domain Events as
                           Behaviors
             NewTrade            AddValueDate               EnrichTr...
.. and since we store every event that hits the system we
       have the ability to recreate ANY previous state of the
  ...
Events and States
                       sealed trait TradingEvent extends Event

                       case   object   N...
The Current State
                       • How do you reflect the current state of the
                         domain obje...
The essence of Event
                             Sourcing
                       Store the events in a durable repository...
The Duality
                       •   Event Sourcing keeps a     •   In functional
                           trail of al...
Event Sourced Domain
                              Models
                       • Now we have the domain objects receivin...
Separation of concerns

                       • The Trade object has the core business of
                         the tr...
Separation of Concerns
                       •   The service layer          •   The core domain layer

                  ...
The Domain Service
                              Layer
                       • Handles domain events and delegates to
   ...
CQRS
                       • The service layer ensures a complete
                         decoupling of how it handles u...
In other words, the domain
                       service layer acts as a state
                                 machine
T...
Making it Explicit
                       • Model the domain service layer as an FSM
                         (Finite Stat...
FSM in Akka
                       • Actor based
                        ✦   available as a mixin for the Akka actor
     ...
initial state

                                                match on event
                                            ...
Handle events &
                                               process data updates and state
                            ...
State change -
                              functionally
                  // closure for adding a value date
           ...
Notifying Listeners

        • A typical use case is to send
        updates to the Query
        subscribers as in CQRS

...
Notifications in Akka
                              FSM
               trait FSM[S, D] extends Listeners {
                ...
Event Logging

                       • Log asynchronously
                       • Persist the current state along with t...
Event Logging
            case class EventLogEntry(entryId: Long,
              objectId: String,
              inState: S...
Log Anywhere
Thursday 12 April 12
Order preserving
                           logging
 class RedisEventLog(clients: RedisClientPool, as: ActorSystem)
   ext...
case class LogEvent(objectId: String, state: State,
   data: Option[Any], event: Event)

 class Logger(clients: RedisClien...
def appendAsync(id: String, state: State,
    data: Option[Any], event: Event): Future[EventLogEntry] =

            (logg...
class TradeLifecycle(trade: Trade, timeout: Duration,
          log: Option[EventLog])
          extends Actor with FSM[Tr...
// 1. create the state machine
               val tlc =
                 system.actorOf(Props(
                   new Trad...
// check query store
                 val f = qry ? QueryAllTrades
                 val qtrades = Await.result(f,timeout.d...
Summary
                       • Event sourcing is an emerging trend in
                         architecting large system...
Summary
                       • Complicated in-memory snapshot may have
                         some performance overhea...
Thank You!



Thursday 12 April 12
Upcoming SlideShare
Loading in …5
×

of

Functional and Event Driven - another approach to domain modeling Slide 1 Functional and Event Driven - another approach to domain modeling Slide 2 Functional and Event Driven - another approach to domain modeling Slide 3 Functional and Event Driven - another approach to domain modeling Slide 4 Functional and Event Driven - another approach to domain modeling Slide 5 Functional and Event Driven - another approach to domain modeling Slide 6 Functional and Event Driven - another approach to domain modeling Slide 7 Functional and Event Driven - another approach to domain modeling Slide 8 Functional and Event Driven - another approach to domain modeling Slide 9 Functional and Event Driven - another approach to domain modeling Slide 10 Functional and Event Driven - another approach to domain modeling Slide 11 Functional and Event Driven - another approach to domain modeling Slide 12 Functional and Event Driven - another approach to domain modeling Slide 13 Functional and Event Driven - another approach to domain modeling Slide 14 Functional and Event Driven - another approach to domain modeling Slide 15 Functional and Event Driven - another approach to domain modeling Slide 16 Functional and Event Driven - another approach to domain modeling Slide 17 Functional and Event Driven - another approach to domain modeling Slide 18 Functional and Event Driven - another approach to domain modeling Slide 19 Functional and Event Driven - another approach to domain modeling Slide 20 Functional and Event Driven - another approach to domain modeling Slide 21 Functional and Event Driven - another approach to domain modeling Slide 22 Functional and Event Driven - another approach to domain modeling Slide 23 Functional and Event Driven - another approach to domain modeling Slide 24 Functional and Event Driven - another approach to domain modeling Slide 25 Functional and Event Driven - another approach to domain modeling Slide 26 Functional and Event Driven - another approach to domain modeling Slide 27 Functional and Event Driven - another approach to domain modeling Slide 28 Functional and Event Driven - another approach to domain modeling Slide 29 Functional and Event Driven - another approach to domain modeling Slide 30 Functional and Event Driven - another approach to domain modeling Slide 31 Functional and Event Driven - another approach to domain modeling Slide 32 Functional and Event Driven - another approach to domain modeling Slide 33 Functional and Event Driven - another approach to domain modeling Slide 34 Functional and Event Driven - another approach to domain modeling Slide 35 Functional and Event Driven - another approach to domain modeling Slide 36 Functional and Event Driven - another approach to domain modeling Slide 37 Functional and Event Driven - another approach to domain modeling Slide 38 Functional and Event Driven - another approach to domain modeling Slide 39 Functional and Event Driven - another approach to domain modeling Slide 40 Functional and Event Driven - another approach to domain modeling Slide 41 Functional and Event Driven - another approach to domain modeling Slide 42 Functional and Event Driven - another approach to domain modeling Slide 43 Functional and Event Driven - another approach to domain modeling Slide 44 Functional and Event Driven - another approach to domain modeling Slide 45 Functional and Event Driven - another approach to domain modeling Slide 46 Functional and Event Driven - another approach to domain modeling Slide 47 Functional and Event Driven - another approach to domain modeling Slide 48 Functional and Event Driven - another approach to domain modeling Slide 49 Functional and Event Driven - another approach to domain modeling Slide 50 Functional and Event Driven - another approach to domain modeling Slide 51 Functional and Event Driven - another approach to domain modeling Slide 52 Functional and Event Driven - another approach to domain modeling Slide 53 Functional and Event Driven - another approach to domain modeling Slide 54 Functional and Event Driven - another approach to domain modeling Slide 55 Functional and Event Driven - another approach to domain modeling Slide 56 Functional and Event Driven - another approach to domain modeling Slide 57
Upcoming SlideShare
Decoupling with Domain Events
Next
Download to read offline and view in fullscreen.

22 Likes

Share

Download to read offline

Functional and Event Driven - another approach to domain modeling

Download to read offline

The presentation I gave at PhillyETE 2012

Related Books

Free with a 30 day trial from Scribd

See all

Functional and Event Driven - another approach to domain modeling

  1. 1. Functional & Event Driven another approach to domain modeling Thursday 12 April 12
  2. 2. Debasish Ghosh @debasishg on Twitter code @ http://github.com/debasishg blog @ Ruminations of a Programmer http://debasishg.blogspot.com Thursday 12 April 12
  3. 3. Agenda • Domain Models • Why Functional Domain Models • Event based architectures - Event Sourcing • Duality between functional models & even based models • A sample event sourced domain model implementation Thursday 12 April 12
  4. 4. Domain Modeling • We limit ourselves strictly to how the domain behaves internally and how it responds to events that it receives from the external context • We think of a domain model as being comprised of the core granular abstractions that handle the business logic and a set of coarser level services that interacts with the external world Thursday 12 April 12
  5. 5. Why Functional ? • Pure with localization of side-effects • Ability to reason about your model • Expressive & Declarative • Immutable Thursday 12 April 12
  6. 6. A Functional Domain Model • Built on (mostly) pure abstractions • Pure functions to model domain behaviors • Immutable artifacts • .. and you get some parallelism for free Thursday 12 April 12
  7. 7. Immutability • Algebraic Data Types for representation of domain entities • Immutable structures like type-lenses for functional updates • No in-place mutation Thursday 12 April 12
  8. 8. Representation as ADTs case class Trade(account: Account, instrument: Instrument, refNo: String, market: Market, unitPrice: BigDecimal, quantity: BigDecimal, tradeDate: Date = Calendar.getInstance.getTime, valueDate: Option[Date] = None, taxFees: Option[List[(TaxFeeId, BigDecimal)]] = None, netAmount: Option[BigDecimal] = None) { override def equals(that: Any) = refNo == that.asInstanceOf[Trade].refNo override def hashCode = refNo.hashCode } Thursday 12 April 12
  9. 9. Representation as ADTs // various tax/fees to be paid when u do a trade sealed trait TaxFeeId extends Serializable case object TradeTax extends TaxFeeId case object Commission extends TaxFeeId case object VAT extends TaxFeeId Thursday 12 April 12
  10. 10. Pure functions as domain behaviors // get the list of tax/fees applicable for this trade // depends on the market val forTrade: Trade => Option[List[TaxFeeId]] = {trade => taxFeeForMarket.get(trade.market) <+> taxFeeForMarket.get(Other) } Thursday 12 April 12
  11. 11. Pure functions as domain behaviors // enrich a trade with tax/fees and compute net value val enrichTrade: Trade => Trade = {trade => val taxes = for { taxFeeIds <- forTrade // get the tax/fee ids for a trade taxFeeValues <- taxFeeCalculate // calculate tax fee values } yield(taxFeeIds map taxFeeValues) val t = taxFeeLens.set(trade, taxes(trade)) netAmountLens.set(t, t.taxFees.map(_.foldl(principal(t))((a, b) => a + b._2))) } Thursday 12 April 12
  12. 12. Pure functions as domain behaviors // combinator to value a tax/fee for a specific trade val valueAs: Trade => TaxFeeId => BigDecimal = {trade => tid => ((rates get tid) map (_ * principal(trade))) getOrElse (BigDecimal(0)) } // all tax/fees for a specific trade val taxFeeCalculate: Trade => List[TaxFeeId] => List[(TaxFeeId, BigDecimal)] = {t => tids => tids zip (tids map valueAs(t)) } Thursday 12 April 12
  13. 13. Composability Secret sauce ? Functions compose. We can pass functions as first class citizens in the paradigm of functional programming. We can use them as return values and if we can make them pure we can treat them just as mathematically as you would like to. Thursday 12 April 12
  14. 14. Updating a Domain Structure functionally • A Type-Lens is a data structure that sets up a bidirectional transformation between a set of source structures S and target structures T • A Type-Lens is set up as a pair of functions: • get S -> T • putBack (S X T) -> S Thursday 12 April 12
  15. 15. A Type Lens in Scala // change ref no val refNoLens: Lens[Trade, String] = Lens((t: Trade) => t.refNo, (t: Trade, r: String) => t.copy(refNo = r)) a function that takes a trade and returns it’s reference no a function that updates a trade with a supplied reference no Thursday 12 April 12
  16. 16. Lens under Composition • What’s the big deal with a Type Lens ? ✦ Lens compose and hence gives you a cool syntax to update nested structures within an ADT def addressL: Lens[Person, Address] = ... def streetL: Lens[Address, String] = ... val personStreetL: Lens[Person, String] = streetL compose addressL Thursday 12 April 12
  17. 17. Lens under composition Using the personStreetL lens we may access or set the (indirect) street property of a Person instance val str: String = personStreetL get person val newP: Person = personStreetL set (person, "Bob_St") Thursday 12 April 12
  18. 18. Functional updates using type lens // change ref no val refNoLens: Lens[Trade, String] = Lens((t: Trade) => t.refNo, (t: Trade, r: String) => t.copy(refNo = r)) // add tax/fees val taxFeeLens: Lens[Trade, Option[List[(TaxFeeId, BigDecimal)]]] = Lens((t: Trade) => t.taxFees, (t: Trade, tfs: Option[List[(TaxFeeId, BigDecimal)]]) => t.copy(taxFees = tfs)) // add net amount val netAmountLens: Lens[Trade, Option[BigDecimal]] = Lens((t: Trade) => t.netAmount, (t: Trade, n: Option[BigDecimal]) => t.copy(netAmount = n)) // add value date val valueDateLens: Lens[Trade, Option[Date]] = Lens((t: Trade) => t.valueDate, (t: Trade, d: Option[Date]) => t.copy(valueDate = d)) Thursday 12 April 12
  19. 19. Managing States • But domain objects don’t exist in isolation • Need to interact with other objects • .. and respond to events from the external world • .. changing from one state to another Thursday 12 April 12
  20. 20. A day in the life of a Trade object tax/fee added created value date added net value computed ssi added a Trade object ready for settlement Thursday 12 April 12
  21. 21. What’s the big deal ? All these sound like changing states of a newly created Trade object !! Thursday 12 April 12
  22. 22. • but .. • Changing state through in-place mutation is destructive • We lose temporality of the data structure • The fact that a Trade is enriched with tax/ fee NOW does not mean it was not valued at 0 tax SOME TIME BACK Thursday 12 April 12
  23. 23. What if we would like to have our system rolled back to THAT POINT IN TIME ? Thursday 12 April 12
  24. 24. We are just being lossy Thursday 12 April 12
  25. 25. • The solution is to keep information in such a way that we have EVERY BIT OF DETAILS stored as the object changes from one state to another • Enter Event Sourcing Thursday 12 April 12
  26. 26. Event Sourcing • Preserves the temporality of the data structure • Represent state NOT as a mutable object, rather as a sequence of domain events that took place right from the creation till the current point in time • Decouple the state of the object from its identity. The state changes over time, identity is IMMUTABLE Thursday 12 April 12
  27. 27. Domain Events as Behaviors NewTrade AddValueDate EnrichTrade state = Created state = ValueDateAdded state = Enriched persisted with timestamp t0 persisted with timestamp t2 persisted with timestamp t1 (t2 > t1 > t0) Thursday 12 April 12
  28. 28. .. and since we store every event that hits the system we have the ability to recreate ANY previous state of the system starting from ANY point in time in the past Thursday 12 April 12
  29. 29. Events and States sealed trait TradingEvent extends Event case object NewTrade extends TradingEvent case object EnrichTrade extends TradingEvent case object AddValueDate extends TradingEvent case object SendOutContractNote extends TradingEvent sealed trait TradeState extends State case object Created extends TradeState case object Enriched extends TradeState case object ValueDateAdded extends TradeState Thursday 12 April 12
  30. 30. The Current State • How do you reflect the current state of the domain object ? ✦ start with the initial state ✦ manage all state transitions ✦ persist all state transitions ✦ maintain a snapshot that reflects the current state ✦ you now have the ability to roll back to any earlier state Thursday 12 April 12
  31. 31. The essence of Event Sourcing Store the events in a durable repository.They are the lowest level granular structure that model the actual behavior of the domain.You can always recreate any state if you store events since the creation of the domain object. Thursday 12 April 12
  32. 32. The Duality • Event Sourcing keeps a • In functional trail of all events that programming, data the abstraction has structures that keep handled track of their history are called persistent data • Event Sourcing does not structures. Immutability taken to the next level ever mutate an existing record • An immutable data structure does not mutate data - returns a newer version every time you update it Thursday 12 April 12
  33. 33. Event Sourced Domain Models • Now we have the domain objects receiving domain events which take them from state A to state B • Will the Trade object have all the event handling logic ? • What about state changes ? Use the Trade object for this as well ? Thursday 12 April 12
  34. 34. Separation of concerns • The Trade object has the core business of the trading logic. It manifests all state changes in terms of what it contains as data • But event handling and managing state transitions is something that belongs to the service layer of the domain model Thursday 12 April 12
  35. 35. Separation of Concerns • The service layer • The core domain layer ✦ receives events from ✦ implements core the context trading functions like calculation of value ✦ manages state date, trade valuation, transitions tax/fee handling etc ✦ delegates to Trade ✦ completely oblivious object for core of the context business ✦ notifies other subscribers Thursday 12 April 12
  36. 36. The Domain Service Layer • Handles domain events and delegates to someone who logs (sources) the events • May need to maintain an in-memory snapshot of the domain object’s current state • Delegates persistence of the snapshot to other subscribers like Query Services Thursday 12 April 12
  37. 37. CQRS • The service layer ensures a complete decoupling of how it handles updates (commands) on domain objects and reads (queries) on the recent snapshot • Updates are persisted as events while queries are served from entirely different sources, typically from read slaves in an RDBMS Thursday 12 April 12
  38. 38. In other words, the domain service layer acts as a state machine Thursday 12 April 12
  39. 39. Making it Explicit • Model the domain service layer as an FSM (Finite State Machine) - call it the TradeLifecycle, which is totally segregated from the Trade domain object • .. and let the FSM run within an actor model based on asynchronous messaging • We use Akka’s FSM implementation Thursday 12 April 12
  40. 40. FSM in Akka • Actor based ✦ available as a mixin for the Akka actor ✦ similar to Erlang gen_fsm implementation ✦ as a client you need to implement the state transition rules using a declarative syntax based DSL, all heavy lifting done by Akka actors Thursday 12 April 12
  41. 41. initial state match on event AddValueDate startWith(Created, trade) start state of Trade object when(Created) { case Event(e@AddValueDate, data) => log.map(_.appendAsync(data.refNo, Created, Some(data), e)) val trd = addValueDate(data) gossip(trd) goto(ValueDateAdded) using trd forMax(timeout) } notify observers log the event move to next state update data of Trade lifecycle structure Thursday 12 April 12
  42. 42. Handle events & process data updates and state changes startWith(Created, trade) Log events (event sourcing) when(Created) { case Event(e@AddValueDate, data) => log.map(_.appendAsync(data.refNo, Created, Some(data), e)) val trd = addValueDate(data) gossip(trd) goto(ValueDateAdded) using trd forMax(timeout) } Notifying Listeners Thursday 12 April 12
  43. 43. State change - functionally // closure for adding a value date val addValueDate: Trade => Trade = {trade => valueDateLens.set(trade, ..) } pure referentially transparent implementation Thursday 12 April 12
  44. 44. Notifying Listeners • A typical use case is to send updates to the Query subscribers as in CQRS • The query is rendered from a separate store which needs to be updated with all changes that go on in the domain model Thursday 12 April 12
  45. 45. Notifications in Akka FSM trait FSM[S, D] extends Listeners { //.. } Listeners is a generic trait to implement listening capability on an Actor trait Listeners {self: Actor => protected val listeners = .. //.. protected def gossip(msg: Any) = listeners foreach (_ ! msg) //.. } Thursday 12 April 12
  46. 46. Event Logging • Log asynchronously • Persist the current state along with the data at this state • Log the event e log.map(_.appendAsync(data.refNo, Created, Some(data), e)) Thursday 12 April 12
  47. 47. Event Logging case class EventLogEntry(entryId: Long, objectId: String, inState: State, withData: Option[Any], event: Event) trait EventLog extends Iterable[EventLogEntry] { def iterator: Iterator[EventLogEntry] def iterator(fromEntryId: Long): Iterator[EventLogEntry] def appendAsync(id: String, state: State, data: Option[Any], event: Event): Future[EventLogEntry] } Thursday 12 April 12
  48. 48. Log Anywhere Thursday 12 April 12
  49. 49. Order preserving logging class RedisEventLog(clients: RedisClientPool, as: ActorSystem) extends EventLog { val loggerActorName = "redis-event-logger" // need a pinned dispatcher to maintain order of log entries lazy val logger = as.actorOf(Props(new Logger(clients)).withDispatcher("my-pinned-dispatcher"), name = loggerActorName) akka { //.. actor { } timeout = 20 my-pinned-dispatcher { executor = "thread-pool-executor" type = PinnedDispatcher } } } Thursday 12 April 12
  50. 50. case class LogEvent(objectId: String, state: State, data: Option[Any], event: Event) class Logger(clients: RedisClientPool) extends Actor { implicit val format = Format {case l: EventLogEntry => serializeEventLogEntry(l)} implicit val parseList = Parse[EventLogEntry](deSerializeEventLogEntry(_)) def receive = { case LogEvent(id, state, data, event) => val entry = EventLogEntry(RedisEventLog.nextId(), id, state, data, event) clients.withClient {client => client.lpush(RedisEventLog.logName, entry) } sender ! entry //.. } //.. logger is an actor that pushes event } data into Redis Thursday 12 April 12
  51. 51. def appendAsync(id: String, state: State, data: Option[Any], event: Event): Future[EventLogEntry] = (logger ? LogEvent(id, state, data, event)) .asInstanceOf[Future[EventLogEntry]] Non-blocking : returns a Future Thursday 12 April 12
  52. 52. class TradeLifecycle(trade: Trade, timeout: Duration, log: Option[EventLog]) extends Actor with FSM[TradeState, Trade] { import FSM._ startWith(Created, trade) domain service as a Finite State Machine when(Created) { case Event(e@AddValueDate, data) => log.map(_.appendAsync(data.refNo, Created, Some(data), e)) • declarative val trd = addValueDate(data) • actor based gossip(trd) • asynchronous goto(ValueDateAdded) using trd forMax(timeout) • event sourced } when(ValueDateAdded) { case Event(StateTimeout, _) => stay case Event(e@EnrichTrade, data) => log.map(_.appendAsync(data.refNo, ValueDateAdded, None, e)) val trd = enrichTrade(data) gossip(trd) goto(Enriched) using trd forMax(timeout) } //.. } Thursday 12 April 12
  53. 53. // 1. create the state machine val tlc = system.actorOf(Props( new TradeLifecycle(trd, timeout.duration, Some(log)))) // 2. register listeners tlc ! SubscribeTransitionCallBack(qry) // 3. fire events tlc ! AddValueDate tlc ! EnrichTrade val future = tlc ? SendOutContractNote // 4. wait for result finalTrades += Await.result(future, timeout.duration) .asInstanceOf[Trade] Thursday 12 April 12
  54. 54. // check query store val f = qry ? QueryAllTrades val qtrades = Await.result(f,timeout.duration) .asInstanceOf[List[Trade]] // query store in sync with the in-memory snapshot qtrades should equal(finalTrades) Thursday 12 April 12
  55. 55. Summary • Event sourcing is an emerging trend in architecting large systems • Focuses on immutability and hence plays along well with functional programming principles • Event stores are append only - hence n locks, no synchronization - very fast at the event storage layer Thursday 12 April 12
  56. 56. Summary • Complicated in-memory snapshot may have some performance overhead, but can be managed with a good STM implementation • In designing your domain model based on event sourcing, make your core model abstractions meaty enough while leaving the service layer with the responsibilities of managing event handling and state transitions Thursday 12 April 12
  57. 57. Thank You! Thursday 12 April 12
  • SunghwanCho8

    Dec. 27, 2020
  • yiqing

    Feb. 10, 2018
  • AlexBilenko1

    Apr. 10, 2017
  • GabrielGiussi

    Jan. 27, 2016
  • ssusere8202a

    Aug. 16, 2015
  • mythii

    May. 10, 2015
  • MichaelJorgeGmezCamp

    Apr. 7, 2015
  • bunkertor

    Feb. 23, 2015
  • deepcool14

    Feb. 5, 2015
  • hrxone

    Nov. 17, 2014
  • willleeworks

    Jul. 22, 2013
  • sudharshancbe

    Jun. 13, 2013
  • ivanluzyanin

    Apr. 12, 2013
  • bigkahuna1uk

    Dec. 28, 2012
  • filosganga

    Oct. 18, 2012
  • eryzhikov

    Jul. 8, 2012
  • JoeTapana

    Jul. 4, 2012
  • gurkenknecht

    Jun. 25, 2012
  • flyflee

    May. 27, 2012
  • simshi

    Apr. 19, 2012

The presentation I gave at PhillyETE 2012

Views

Total views

10,934

On Slideshare

0

From embeds

0

Number of embeds

107

Actions

Downloads

219

Shares

0

Comments

0

Likes

22

×