< Return to Video

RailsConf 2014 - Domain Driven Design and Hexagonal Architecture with Rails

  • 0:18 - 0:20
    ERIC ROBERT: All right, hi. I'm Eric Roberts
  • 0:20 - 0:21
    and I'm here to talk to you all about
  • 0:21 - 0:23
    how you can use software design patterns
  • 0:23 - 0:25
    to put your Rails app on a diet
  • 0:25 - 0:29
    and make your tests run really, really fast.
  • 0:36 - 0:38
    Jokes aside, we will be telling you about
    some
  • 0:38 - 0:42
    design patterns. Not so much making your tests
    run
  • 0:42 - 0:45
    fast. But we're here to talk about domain-driven
    design
  • 0:45 - 0:47
    hexagonal architecture in Rails.
  • 0:47 - 0:49
    I'm Eric Roberts. I'm a software developer
    and I
  • 0:49 - 0:53
    work at a company in Waterloo, Ontario, Canada
    called
  • 0:53 - 0:57
    Boltmade. I met Declan when we worked together
    at
  • 0:57 - 1:00
    PrintChomp. Before that, I was a frontend
    developer for
  • 1:00 - 1:03
    a number of years, and I'd worked around Rails
  • 1:03 - 1:06
    applications, but mostly at the view layer
    until Declan
  • 1:06 - 1:10
    dragged me kicking and screaming into backend
    development and
  • 1:10 - 1:12
    made me care about stuff like we're talking
    about
  • 1:12 - 1:14
    today.
  • 1:14 - 1:16
    And this is the biggest crowd I have ever
  • 1:16 - 1:18
    presented in front of. So if you'll excuse
    me,
  • 1:18 - 1:20
    I need to take a picture and email my
  • 1:20 - 1:21
    mom.
  • 1:21 - 1:27
    DECLAN WHELAN: OK. Hi everybody. Real pleasure
    to be
  • 1:27 - 1:29
    here. My name is Declan. I'm the co-founder
    of
  • 1:29 - 1:33
    a company called PrintChomp, and my story
    is about
  • 1:33 - 1:37
    two years ago, I had a opportunity to launch
  • 1:37 - 1:39
    PrintChomp, and I was looking at technologies,
    and I
  • 1:39 - 1:41
    decided that Ruby on Rails was really the
    best
  • 1:41 - 1:45
    platform for us. The only challenge was that
    neither
  • 1:45 - 1:48
    me nor anybody on my team knew Ruby nor
  • 1:48 - 1:49
    Rails.
  • 1:49 - 1:52
    So it was kind of a brave, maybe, decision,
  • 1:52 - 1:55
    but it's one that I don't regret. And one
  • 1:55 - 1:57
    of the things that drew me to the community
  • 1:57 - 1:59
    was the fact that, or, to the platform, was
  • 1:59 - 2:05
    the community around sharing, around the openness
    of, of,
  • 2:05 - 2:08
    of sharing code and, also, a lot about, about
  • 2:08 - 2:12
    the test-focus, which has been pretty important
    to me.
  • 2:12 - 2:14
    But the cool part, I think, about it, was
  • 2:14 - 2:17
    we intentionally took on a lot of technical
    debt,
  • 2:17 - 2:19
    because I knew that, I knew that I would
  • 2:19 - 2:20
    not know enough about our domain. It was a
  • 2:20 - 2:22
    new domain for me, printing, and I also did
  • 2:22 - 2:25
    not know enough about Ruby. I did not know
  • 2:25 - 2:29
    about, enough about Rails. So, very intentionally
    decided to
  • 2:29 - 2:31
    do our best, the best that we could, knowing
  • 2:31 - 2:33
    that we would end up, very likely, with a
  • 2:33 - 2:36
    pretty heaping mound of technical debt.
  • 2:36 - 2:40
    And that turned out to be true. And has
  • 2:40 - 2:44
    anyone had that experience? I don't know.
    Yeah. So,
  • 2:44 - 2:46
    but recently we had an opportunity to build
    an
  • 2:46 - 2:48
    API, and that was really exciting for me.
    And
  • 2:48 - 2:49
    I know there are gonna be some other talks
  • 2:49 - 2:52
    about APIs here later, and I realized that
    I
  • 2:52 - 2:55
    had, I had two kind of competing things that
  • 2:55 - 2:57
    could come together. The first was, I had,
    I
  • 2:57 - 3:00
    had logic in our application that I needed
    to
  • 3:00 - 3:02
    share in our API. How was I going to
  • 3:02 - 3:04
    do that?
  • 3:04 - 3:07
    And all of our code was sprinkled through
    various
  • 3:07 - 3:11
    bits of our controller and model logic. Secondly,
    I
  • 3:11 - 3:13
    wanted a mechanism to, to have a strategy
    for
  • 3:13 - 3:18
    eliminating the technical debt, and what I
    turned to
  • 3:18 - 3:22
    was domain-driven design hexagonal architectures,
    and I want to
  • 3:22 - 3:24
    share what we've learnt along the way of doing
  • 3:24 - 3:26
    that, and, and where we're going. So I hope
  • 3:26 - 3:30
    you're able to learn something from what we've
    done.
  • 3:30 - 3:33
    E.R.: That's great Declan. But what are they
    gonna
  • 3:33 - 3:34
    get out of it?
  • 3:34 - 3:34
    VIDEO
  • 3:34 - 3:37
    ??: Here's a good idea. Have a point! Makes
  • 3:37 - 3:41
    it so much more interesting for the listener!
  • 3:41 - 3:44
    D.W.: Well, that was from Sandy Metz. He said,
  • 3:44 - 3:45
    you know, if you're gonna have a talk, you
  • 3:45 - 3:47
    should really have a point, and I think Steve
  • 3:47 - 3:50
    Martin said it pretty funnily. And I guess,
    our,
  • 3:50 - 3:53
    our number one point is that, there is complexity
  • 3:53 - 3:56
    in the software that we build. There's complexities
    in
  • 3:56 - 3:57
    the problems that we're solving. And we need
    to
  • 3:57 - 4:00
    embrace that complexity and embrace it in
    such a
  • 4:00 - 4:02
    way that we tackle it and deal with it.
  • 4:02 - 4:06
    And deal with it head-on. And by doing that,
  • 4:06 - 4:08
    then we end up having more joy and fun
  • 4:08 - 4:10
    in our work because it's not just about getting
  • 4:10 - 4:12
    this functional piece to work, it's about
    really trying
  • 4:12 - 4:15
    to understand our domain and model it and
    express
  • 4:15 - 4:16
    that in our code and make our code as
  • 4:16 - 4:19
    expressive as possible.
  • 4:19 - 4:22
    And the second thing that I really realized
    was
  • 4:22 - 4:24
    I knew that our code was a mess, and
  • 4:24 - 4:27
    I knew some point refactorings that we could
    do,
  • 4:27 - 4:29
    but I didn't know how to, how to, you
  • 4:29 - 4:31
    know, what did the end look like? If it
  • 4:31 - 4:34
    was refactored significantly, what would it
    look like? What
  • 4:34 - 4:37
    would the shape look like? What would the
    namespaces
  • 4:37 - 4:39
    be? What would the classes be doing, et cetera.
  • 4:39 - 4:43
    And domain-driven design and hexagonal architecture
    helped me envision
  • 4:43 - 4:45
    and share with my team what it might look
  • 4:45 - 4:46
    like.
  • 4:46 - 4:50
    The third thing that, that, that we would
    like
  • 4:50 - 4:52
    you to take away from this talk is that
  • 4:52 - 4:55
    there's a lot more to just being a Rails
  • 4:55 - 4:59
    developer, the, the ideas and patterns, if
    you will,
  • 4:59 - 5:01
    that we, that we're talking about are, some
    of
  • 5:01 - 5:03
    them have been written by some of those people
  • 5:03 - 5:08
    that DHH had in his slide, including James
    Coplain.
  • 5:08 - 5:10
    But a lot of those patterns have been around
  • 5:10 - 5:12
    for a long time. SO those patterns and those
  • 5:12 - 5:16
    ideas and those practices will serve you well
    beyond
  • 5:16 - 5:19
    Rails. They would work in a node application.
    They
  • 5:19 - 5:21
    could work in a desktop application. They
    can work
  • 5:21 - 5:23
    in a wide variety of areas. So by getting
  • 5:23 - 5:26
    some familiarity with these concepts, you're
    able to transfer
  • 5:26 - 5:30
    those skills to things beyond Rails.
  • 5:30 - 5:33
    And so I want to ask Eric just to
  • 5:33 - 5:35
    walk us through kind of some of the, some
  • 5:35 - 5:37
    of the pain that we had with our initial
  • 5:37 - 5:39
    Rails development and see if it resonates
    with you.
  • 5:39 - 5:44
    E.R.: All right. So everyone knows what this
    is.
  • 5:44 - 5:48
    It's a Rails folder structure. And it's really
    great
  • 5:48 - 5:49
    when you get started with Rails. You have
    these
  • 5:49 - 5:53
    folders. OK. I, I logically, my things fall
    into
  • 5:53 - 5:56
    these areas. Controllers, models, and views
    are really what
  • 5:56 - 5:59
    we're focusing on right now.
  • 5:59 - 6:01
    But if the responsibility of your code doesn't
    start
  • 6:01 - 6:05
    with M, V or C, what do you do
  • 6:05 - 6:08
    then? And we, and we find it kind of
  • 6:08 - 6:10
    goes like this. You have, you have these areas
  • 6:10 - 6:13
    of responsibility and you have something that
    doesn't really
  • 6:13 - 6:15
    fit, and you don't know where it goes, so
  • 6:15 - 6:16
    you just put it on somewhere and things get
  • 6:16 - 6:18
    a little bigger.
  • 6:18 - 6:20
    And continuing on, you keep doing this, things
    get
  • 6:20 - 6:24
    bigger, and finally, like, the line between
    all of
  • 6:24 - 6:26
    these things is, is blurred. You don't know
    what's
  • 6:26 - 6:30
    what. It's hard to extract reusable parts
    from all
  • 6:30 - 6:32
    of this, because the, the responsibilities
    are split across
  • 6:32 - 6:32
    all these things.
  • 6:32 - 6:34
    And you kind of end up with methods like
  • 6:34 - 6:38
    this. This is one method. It starts on the
  • 6:38 - 6:40
    left and ends on the right. It's about ninety
  • 6:40 - 6:43
    lines.
  • 6:43 - 6:46
    It's not from PrintChomp. It's from another,
    another project
  • 6:46 - 6:49
    that I've worked on. And without setting,
    it doesn't
  • 6:49 - 6:51
    really matter what it does. The point is it's
  • 6:51 - 6:55
    ugly and you know, it is actually about setting
  • 6:55 - 7:00
    prices on properties and the date ranges are
    available.
  • 7:00 - 7:03
    So I have a question. We need to add
  • 7:03 - 7:05
    sales tax to the prices.
  • 7:05 - 7:10
    Anybody care to take on that refactoring?
  • 7:10 - 7:12
    It's pretty horrible. But nobody, nobody sits
    down and
  • 7:12 - 7:14
    says, I'm gonna write a ninety line method
    today
  • 7:14 - 7:20
    that does property prices with date ranges.
    The initial
  • 7:20 - 7:23
    spec was probably something a lot simpler.
    And because
  • 7:23 - 7:26
    it's in that spot now, the next person who
  • 7:26 - 7:27
    comes, comes and looks and that method and
    goes,
  • 7:27 - 7:28
    oh, well OK, well if I just type this
  • 7:28 - 7:32
    little bit more it'll, it'll do that now.
  • 7:32 - 7:34
    And this was a little bit of a train
  • 7:34 - 7:37
    wreck. At some point you probably, the people
    working
  • 7:37 - 7:39
    on this, including myself, probably should
    have realized, you
  • 7:39 - 7:41
    know, forty-five lines might have been the
    time to
  • 7:41 - 7:46
    split it up. Maybe twenty. Maybe less.
  • 7:46 - 7:48
    But this is, nonetheless, what we ended up
    with.
  • 7:48 - 7:51
    Or, you end up with Rube Goldberg machines
    for
  • 7:51 - 7:53
    sharpening pencils.
  • 7:53 - 7:56
    So there's a lot of well-known patterns that
    can
  • 7:56 - 7:58
    help you out with this. Has anyone read this
  • 7:58 - 8:03
    blog post? Yeah. Anybody use any of the patterns
  • 8:03 - 8:04
    in them?
  • 8:04 - 8:07
    They're pretty great, right. We use a lot
    of
  • 8:07 - 8:09
    them, and I'm, we're gonna tell you about
    some
  • 8:09 - 8:13
    of them here. But the one thing you've, so
  • 8:13 - 8:16
    you've extracted all of these things into
    small responsibilities.
  • 8:16 - 8:18
    Your models, your controllers and your views.
    They're all
  • 8:18 - 8:19
    small again.
  • 8:19 - 8:22
    But, OK, so what? I have a bunch of
  • 8:22 - 8:25
    little objects that all go, they all know
    too
  • 8:25 - 8:28
    much about each other. They don't fit in any
  • 8:28 - 8:32
    logical structure. They don't, how, how does
    this all
  • 8:32 - 8:35
    fit together? You've made, like, an awesome
    first step,
  • 8:35 - 8:37
    in that you have small little pieces that
    you
  • 8:37 - 8:40
    can use. But where do you go from there?
  • 8:40 - 8:44
    And we think that looks kind of like this.
  • 8:44 - 8:50
    We think that your domain concepts, services,
    entities, should
  • 8:50 - 8:53
    be in the middle, and everything else is outside.
  • 8:53 - 8:57
    Your database is an extra concern, your views
    are
  • 8:57 - 8:59
    an extra concern. The web, and you know, when
  • 8:59 - 9:01
    you're designing the API, as Declan talked
    about a
  • 9:01 - 9:03
    little bit, it's an extra concern. So you
    can
  • 9:03 - 9:06
    really focus in on the middle of what your
  • 9:06 - 9:07
    application actually does.
  • 9:07 - 9:09
    I'm gonna let Declan talk a little bit more
  • 9:09 - 9:10
    about that
  • 9:10 - 9:14
    D.W.: Has anyone here heard of domain-driven
    design? Oh,
  • 9:14 - 9:16
    quite a few people. Awesome. How many people
    have
  • 9:16 - 9:20
    actually, you know, intentionally used it
    and, and have
  • 9:20 - 9:23
    worked with it? So, a, a number. SO cool.
  • 9:23 - 9:26
    And it can be quite, it can be quite,
  • 9:26 - 9:30
    it can be quite daunting. And this book by
  • 9:30 - 9:32
    Eric Evans is kind of what kicked it off.
  • 9:32 - 9:34
    And this book was I think written in 2005,
  • 9:34 - 9:36
    and it is a really, really great book, but
  • 9:36 - 9:39
    it is actually quite difficult to read. But
    it's,
  • 9:39 - 9:41
    I think it might be the only technical book
  • 9:41 - 9:43
    I've read twice.
  • 9:43 - 9:44
    And that's partly cause it was really good
    and
  • 9:44 - 9:48
    partly cause it was rather difficult to get
    through
  • 9:48 - 9:49
    some of it. Did anyone else read this book
  • 9:49 - 9:52
    and have a similar experience? like, it's
    really great
  • 9:52 - 9:53
    stuff and you kind of read and it and
  • 9:53 - 9:55
    you go, wow, what does that mean to me?
  • 9:55 - 9:55
    Right?
  • 9:55 - 9:58
    And we, at the end, at the end, we'll
  • 9:58 - 10:00
    be posting our deck, and we'll have some references
  • 10:00 - 10:02
    to other material that, that I've actually
    found to
  • 10:02 - 10:05
    be a bit more addressable or a little more
  • 10:05 - 10:07
    consumable.
  • 10:07 - 10:11
    But what Eric Evans talked about was really
    tackling
  • 10:11 - 10:13
    complexity, and he talks about the, you know,
    the
  • 10:13 - 10:18
    critical complexity is really understanding
    the domain. What are
  • 10:18 - 10:22
    the business rules that take place inside
    our systems?
  • 10:22 - 10:24
    If we're gonna add sales tax, what are the
  • 10:24 - 10:26
    business rules for that sales tax?
  • 10:26 - 10:28
    What are the, what are the rules around who
  • 10:28 - 10:31
    can buy what? And by putting those insides
    and
  • 10:31 - 10:34
    thinking of them as the domain of our system,
  • 10:34 - 10:36
    then we're able to have our outside layers
    just
  • 10:36 - 10:43
    be, if you will, relatively thin facades,
    which allows
  • 10:43 - 10:46
    us to reuse that logic across APIs, across
    other
  • 10:46 - 10:49
    applications. We don't have to duplicate all
    of those
  • 10:49 - 10:51
    business rules.
  • 10:51 - 10:53
    And the way that he proposes to do that
  • 10:53 - 10:56
    is through ubiquitous language. And this is
    a picture
  • 10:56 - 10:59
    of the tower of Babel and if anybody knows
  • 10:59 - 11:02
    the story, it's where everyone in the world
    was
  • 11:02 - 11:05
    speaking a different language and, I once
    worked on
  • 11:05 - 11:09
    a project that was a financial transaction
    processing system,
  • 11:09 - 11:12
    and when I inherited it, the guy who was
  • 11:12 - 11:16
    proceeded me had this, was a model train aficionado,
  • 11:16 - 11:19
    so he had the idea of model trains. So
  • 11:19 - 11:22
    every transaction was like a car, and the
    payment
  • 11:22 - 11:26
    engines were, were train tracks. And he had
    all
  • 11:26 - 11:29
    of these metaphors around transaction processing
    and trains, and
  • 11:29 - 11:30
    was written in Java.
  • 11:30 - 11:32
    So when I was asked to add a new
  • 11:32 - 11:34
    feature, fix a bug, I had to understand the
  • 11:34 - 11:36
    business domain, and then I had to kind of
  • 11:36 - 11:39
    understand, how did that translate into train
    speak, and
  • 11:39 - 11:40
    then I had to go look at the Java
  • 11:40 - 11:43
    code to figure it all out, right. So the
  • 11:43 - 11:46
    coding was easy. The hard part was really
    trying
  • 11:46 - 11:48
    to understand what was being asked and how
    did
  • 11:48 - 11:51
    the code express that, right?
  • 11:51 - 11:52
    And that's what Eric Evans is talking about
    with
  • 11:52 - 11:55
    ubiquitous language. We want to have the language
    that
  • 11:55 - 11:59
    we speak with domain experts should be readable
    in
  • 11:59 - 12:01
    the code. If I'm order, if I'm a customer
  • 12:01 - 12:04
    and I can purchase a product, there should
    be
  • 12:04 - 12:06
    a class called customer. There should be a
    class
  • 12:06 - 12:08
    called product. And there should be a verb
    in
  • 12:08 - 12:11
    there that's somewhere that says purchase,
    et cetera.
  • 12:11 - 12:14
    So that there's minimal translation between
    the domain experts'
  • 12:14 - 12:17
    language and the language that my code is
    written
  • 12:17 - 12:19
    in. Ruby gives us a great opportunity to do
  • 12:19 - 12:22
    that, but this is much more difficult in,
    in
  • 12:22 - 12:27
    other more statically typed languages.
  • 12:27 - 12:28
    So that's kind of the, the key thing to
  • 12:28 - 12:31
    take away from domain driven design is to
    try
  • 12:31 - 12:34
    to have your, your concepts expressed in code
    that
  • 12:34 - 12:37
    are meaningful in the words people use.
  • 12:37 - 12:38
    If they use the word customer, you should
    have
  • 12:38 - 12:41
    a customer class. If they use the word user,
  • 12:41 - 12:44
    you should have a user class, et cetera.
  • 12:44 - 12:46
    But beyond that, it also has some kind of
  • 12:46 - 12:50
    key, I, I dare not say it, patterns? Now,
  • 12:50 - 12:54
    I'm almost nervous now to say it.
  • 12:54 - 13:01
    But let me be clear. This isn't science. It's
  • 13:02 - 13:06
    not, right. Because these patterns were not
    dreamed up
  • 13:06 - 13:11
    in academia ivory towers. These patterns that
    we talk
  • 13:11 - 13:15
    about were empirically derived from people
    intentionally doing what
  • 13:15 - 13:18
    we do, which is write code every day, intentionally
  • 13:18 - 13:20
    thinking about, how does that, how does those,
    how
  • 13:20 - 13:22
    do those things fit together, and what is
    the
  • 13:22 - 13:24
    essence of what I'm doing?
  • 13:24 - 13:26
    And can I extract that into words that I
  • 13:26 - 13:29
    can use to communicate with other people?
    The beauty
  • 13:29 - 13:31
    of patterns is I can talk about a value
  • 13:31 - 13:33
    object, and if you know what a value object
  • 13:33 - 13:36
    is and I know what a value object is,
  • 13:36 - 13:38
    we can have a much richer conversation than,
    oh,
  • 13:38 - 13:40
    I have to have an object whose state is,
  • 13:40 - 13:42
    whose identity is defined by the state of
    its
  • 13:42 - 13:44
    attributes and then, you know, we can have
    a
  • 13:44 - 13:45
    much richer conversation.
  • 13:45 - 13:48
    E.R.: You might say you ubiquitous language.
  • 13:48 - 13:50
    D.W.: Yeah. So we're gonna touch a little
    bit
  • 13:50 - 13:53
    on some of these patterns. But, but the idea
  • 13:53 - 13:59
    is that these are not academic ivory tower
    concepts.
  • 13:59 - 14:01
    These are empirically driven from people who've
    worked in
  • 14:01 - 14:03
    the field. And if we're gonna, if we're gonna
  • 14:03 - 14:06
    be successful as an organization, while I
    fully agree
  • 14:06 - 14:08
    that we need to go out and write code
  • 14:08 - 14:10
    and we need to read code, I totally think
  • 14:10 - 14:12
    that's true, but we also have to have more
  • 14:12 - 14:16
    effective ways of communicating knowledge,
    so that we're not
  • 14:16 - 14:18
    all learning the same things from each other
    over
  • 14:18 - 14:19
    and over. WE can learn more easily from each
  • 14:19 - 14:23
    other. And that's what these patterns are.
  • 14:23 - 14:27
    And the next kind of piece that we're gonna
  • 14:27 - 14:30
    talk about is hexagonal architectures. And
    it's more than
  • 14:30 - 14:33
    just that. I think we've alluded to a little
  • 14:33 - 14:36
    bit. It's really the idea of, you're going
    to
  • 14:36 - 14:42
    have this core domain in the middle, and in,
  • 14:42 - 14:45
    inside your code, surrounding your outside
    core are gonna
  • 14:45 - 14:49
    be some application level code that expresses
    the rules
  • 14:49 - 14:51
    of your application. And I would draw it slightly
  • 14:51 - 14:54
    differently, perhaps, but, and then we have
    adapters on
  • 14:54 - 14:57
    the outside that adapt that code to web calls
  • 14:57 - 15:01
    or database calls or SNTP, or in my case,
  • 15:01 - 15:02
    APIs.
  • 15:02 - 15:04
    And so that's the way that we want you
  • 15:04 - 15:06
    to, that's the way that we are starting to
  • 15:06 - 15:08
    approach the work at PrintChomp is thinking
    about it
  • 15:08 - 15:11
    in those constructs. And now we want to jump
  • 15:11 - 15:14
    to some specific patterns and, and show you
    some
  • 15:14 - 15:16
    real code that actually, you know, brings
    these to
  • 15:16 - 15:20
    life. So Eric, you want to talk about form
  • 15:20 - 15:20
    object?
  • 15:20 - 15:22
    E.R.: Sure. Unfortunately, I can't tell you
    what your
  • 15:22 - 15:24
    domain is or what necessarily goes in the
    middle
  • 15:24 - 15:27
    of that hexagon. But I can give you some
  • 15:27 - 15:30
    ways to keep other things out of that hexagon
  • 15:30 - 15:32
    that you don't need to be concerned with.
  • 15:32 - 15:34
    One thing that I've been doing lately is form
  • 15:34 - 15:37
    object. One of the really cool things, you
    know,
  • 15:37 - 15:40
    if you run Rails scaffold, some model name,
    you
  • 15:40 - 15:43
    get a form that you submit, creates the record,
  • 15:43 - 15:47
    edits the record. And that's pretty great.
  • 15:47 - 15:49
    But how do you do that when you don't
  • 15:49 - 15:53
    have a direct one-one mapping with an ActiveModel
    record?
  • 15:53 - 15:57
    So instead of instantiating an ActiveModel
    record, I've taken
  • 15:57 - 16:00
    to instantiating, I'm gonna call it a form
    object.
  • 16:00 - 16:02
    There's a lot of names for a lot of
  • 16:02 - 16:03
    different things. But this is, this is what
    I've
  • 16:03 - 16:05
    been calling it.
  • 16:05 - 16:08
    So here's the actual thing that I was building.
  • 16:08 - 16:10
    On the left hand side, you see that you
  • 16:10 - 16:12
    select a ticket price. On the right hand side,
  • 16:12 - 16:15
    your name, email address, and your payment
    details. And
  • 16:15 - 16:20
    this is actually two ActiveRecord models in
    my database.
  • 16:20 - 16:23
    The, the passengers over there on the right,
    you
  • 16:23 - 16:25
    can add a passenger and keep adding it.
  • 16:25 - 16:27
    And if you've ever worked with nested attributes,
    probably
  • 16:27 - 16:31
    know it's not always that fun. So this is
  • 16:31 - 16:35
    not using nested attributes. It's done like
    this. I
  • 16:35 - 16:39
    have a class TicketForm that includes ActiveModel::Model,
    which is
  • 16:39 - 16:42
    how I get nice things, like that magic initialize
  • 16:42 - 16:46
    method of validators.
  • 16:46 - 16:48
    And the passengers method, if I don't have
    any,
  • 16:48 - 16:50
    returns me a passenger new. That's how it
    puts
  • 16:50 - 16:53
    the name and email address for that first
    passenger.
  • 16:53 - 16:55
    And then tickets, I get out of this by
  • 16:55 - 16:59
    taking my passengers and mapping them into
    new objects.
  • 16:59 - 17:01
    In the controller, it looks a little bit like
  • 17:01 - 17:05
    this. So you just pass your params off into
  • 17:05 - 17:07
    that. You get it back out and you have
  • 17:07 - 17:11
    tickets. So instead of if ticket dot save,
    I
  • 17:11 - 17:16
    do if ticket_form valid and ticketCharger
    charges successfully, then
  • 17:16 - 17:18
    we've had success. And ticketCharge takes
    care of charging
  • 17:18 - 17:21
    my tickets and knowing that, there was only,
    cause
  • 17:21 - 17:23
    there's only one charge for all the tickets,
    right.
  • 17:23 - 17:25
    You're paying all at once. No point to split
  • 17:25 - 17:26
    that up.
  • 17:26 - 17:29
    So that's a really useful thing that I've
    found
  • 17:29 - 17:32
    to, to help when my mappings aren't just totally,
  • 17:32 - 17:33
    I don't want to just take a record of
  • 17:33 - 17:36
    the database, put it in, or update it.
  • 17:36 - 17:37
    And now Declan's gonna tell you a little bit
  • 17:37 - 17:38
    about request objects.
  • 17:38 - 17:40
    D.W.: Yeah. In this case, has anyone used
    a
  • 17:40 - 17:42
    form object or something like that? So quite
    a
  • 17:42 - 17:44
    few people. Cool.
  • 17:44 - 17:46
    Has, has anyone done, used something called
    a request
  • 17:46 - 17:51
    object? Sort of? I was hoping that I invented
  • 17:51 - 17:53
    this. So maybe I haven't. I don't know. OK.
  • 17:53 - 17:56
    But, request object is now in, you know, think
  • 17:56 - 17:58
    of an API as, we're trying to have a
  • 17:58 - 18:03
    similar behavior to the form object, except
    we're trying
  • 18:03 - 18:05
    to take, remember, we're trying to take complexity
    out
  • 18:05 - 18:07
    of our controllers and out of our models and
  • 18:07 - 18:11
    put them into more, to simplify our systems.
  • 18:11 - 18:14
    So the idea with the request object is that
  • 18:14 - 18:15
    we're gonna pull that code out and put it
  • 18:15 - 18:19
    into the, into the controller. And now the
    request
  • 18:19 - 18:21
    object is going to receive the request. And
    the
  • 18:21 - 18:23
    code looks sort of like this, right.
  • 18:23 - 18:27
    There's, I, I've done this slightly differently
    than Eric's,
  • 18:27 - 18:28
    Eric's way, in terms of, of, the kind of
  • 18:28 - 18:30
    the core of the class. The core of this
  • 18:30 - 18:33
    class is using a gem called Verdis, which
    does
  • 18:33 - 18:39
    something similar to ActiveModel::Model, except
    this allows you basically
  • 18:39 - 18:42
    to have a plain-old Ruby object and, the way,
  • 18:42 - 18:45
    and, the cool part is, that, or, I think
  • 18:45 - 18:48
    it's cool. You can, you can declare attributes,
    like
  • 18:48 - 18:50
    this. So I have actually, in my domain layer,
  • 18:50 - 18:52
    I have something called the customer and the
    billing
  • 18:52 - 18:54
    and the shipping. And if, if somebody supplies
    me
  • 18:54 - 18:57
    those three things, then I can complete an
    order
  • 18:57 - 18:59
    in my system.
  • 18:59 - 19:02
    We've, there are a couple of pieces missing
    here,
  • 19:02 - 19:04
    but we wanted to keep it, fit in a
  • 19:04 - 19:07
    slide. But that's basically it. And we have
    the
  • 19:07 - 19:11
    validation. And the cool part about this is
    that
  • 19:11 - 19:12
    when a request comes in, we just, I just
  • 19:12 - 19:15
    put in a before loop in the controller that
  • 19:15 - 19:18
    has a before filter that just basically instantiates
    the
  • 19:18 - 19:24
    request object using some, some reflection
    to figure out,
  • 19:24 - 19:26
    you know, what is the, what is the controller
  • 19:26 - 19:30
    and what is the action being asked, inferring
    the
  • 19:30 - 19:33
    request class, instantiating it, and then
    just passing it
  • 19:33 - 19:35
    what used to be the params hash.
  • 19:35 - 19:37
    But now it's actually a rich object that I
  • 19:37 - 19:40
    can have validations on, et cetera. So the
    net
  • 19:40 - 19:42
    effect of this is that, why would I bother
  • 19:42 - 19:46
    doing this, right? Well, now the controller,
    you know,
  • 19:46 - 19:48
    the complexity around validating the request
    is at the
  • 19:48 - 19:50
    boundary of my system. That doesn't need to
    leak
  • 19:50 - 19:56
    into the rest of my system. It's almost like,
  • 19:56 - 19:57
    you know, does anybody use, you know at the
  • 19:57 - 19:59
    beginning of your methods, you want to put
    the
  • 19:59 - 20:01
    guard clauses to catch the exceptions coming
    in at
  • 20:01 - 20:02
    the beginning of your methods, so that the
    rest
  • 20:02 - 20:04
    of your method is simpler?
  • 20:04 - 20:06
    This is doing the same thing, except it's
    doing
  • 20:06 - 20:08
    it at, at a higher level abstraction, at the
  • 20:08 - 20:10
    API request level. And I, this has worked
    out
  • 20:10 - 20:12
    really well for us. So I, I quite like
  • 20:12 - 20:14
    that one.
  • 20:14 - 20:15
    Yeah.
  • 20:15 - 20:18
    E.R.: Great. Service objects are another one
    that we've
  • 20:18 - 20:21
    been using. I think DHH actually had one in
  • 20:21 - 20:23
    his presentation. I'll give you a hint, he
    didn't
  • 20:23 - 20:27
    like it.
  • 20:27 - 20:30
    He used one, it, it's something instantiated
    by the
  • 20:30 - 20:33
    controller. We've been looking at controllers
    a little bit
  • 20:33 - 20:36
    as just like, OK, I've received this thing,
    pass
  • 20:36 - 20:37
    it off to somebody else, and then do something
  • 20:37 - 20:40
    with the result. So we want to keep out
  • 20:40 - 20:43
    that procedural code from our controllers,
    and service objects
  • 20:43 - 20:46
    are one way you can do that using a
  • 20:46 - 20:49
    order service to create orders, we've encapsulated
    all of
  • 20:49 - 20:54
    the logic about how to create an order in
  • 20:54 - 20:54
    this one area.
  • 20:54 - 20:56
    So if you want to, you can use it
  • 20:56 - 20:58
    from somewhere else, right. You don't have
    to hit
  • 20:58 - 21:02
    a controller action to create an order. It's
    reusable
  • 21:02 - 21:07
    and extendable. And it has Declan's magic
    repository object
  • 21:07 - 21:10
    in there that we'll get to in a bit.
  • 21:10 - 21:14
    And the controller, again, just, it's very
    simple. Do
  • 21:14 - 21:18
    this thing, on success do this, on failure,
    do
  • 21:18 - 21:18
    this.
  • 21:18 - 21:21
    So those are a few of the patterns that
  • 21:21 - 21:26
    we've been using to help us with this. So
  • 21:26 - 21:28
    now what?
  • 21:28 - 21:35
    What's, what's the elephant in the room? Anybody?
  • 21:35 - 21:42
    How do you get them into Rails? Yeah.
  • 21:45 - 21:48
    Yeah. That's the one we were thinking of.
  • 21:48 - 21:51
    D.W.: That's, ActiveRecord. Yeah. I mean,
    I mean, the
  • 21:51 - 21:53
    end, and how do you get into Rails. Yes,
  • 21:53 - 21:56
    there are some interesting challenges around
    that. But I'm
  • 21:56 - 21:58
    gonna flip you back to the architectural slide
    and
  • 21:58 - 22:01
    just point out that you see what's happened
    here
  • 22:01 - 22:04
    is that we're trying to view the application
    pieces
  • 22:04 - 22:07
    of our solution here being really on the perimeter
  • 22:07 - 22:10
    of our core system. And the core system composed
  • 22:10 - 22:14
    of services that may be servicing API or application
  • 22:14 - 22:16
    requests.
  • 22:16 - 22:19
    We have some services that may be invariant
    across
  • 22:19 - 22:21
    any call, and those would be at the very
  • 22:21 - 22:24
    center. And we have things called entities
    which are
  • 22:24 - 22:26
    on the inside. And the key part that's, that
  • 22:26 - 22:29
    we haven't talked about, which actually I'm
    planning the
  • 22:29 - 22:32
    most difficult part in this is this repository
    which
  • 22:32 - 22:34
    is the bottom, which is the r in the
  • 22:34 - 22:36
    bottom right hand corner.
  • 22:36 - 22:38
    And it's job is to talk to the ActiveRecord
  • 22:38 - 22:40
    model which is in the green, and create an
  • 22:40 - 22:42
    entity object which is the blue object, and,
    and
  • 22:42 - 22:45
    how does that actually work? So I'm gonna
    show
  • 22:45 - 22:48
    you a bit of code, and this is, this
  • 22:48 - 22:51
    is the first time I've shown my Ruby code
  • 22:51 - 22:52
    in public. So please be kind.
  • 22:52 - 22:55
    And there are probably way better ways to
    do
  • 22:55 - 22:59
    this. but this is the repository that I've,
    I've,
  • 22:59 - 23:00
    there are some other methods here, but I just
  • 23:00 - 23:02
    wanted to fit on what happen, you know, show
  • 23:02 - 23:04
    you the simple one.
  • 23:04 - 23:06
    So this is what a save looks like. So
  • 23:06 - 23:09
    the save takes a domain object and it converts
  • 23:09 - 23:11
    it through something called a mapper, which
    maps the
  • 23:11 - 23:15
    domain object onto a ActiveRecord object,
    which is called
  • 23:15 - 23:19
    a record here. Then I, then it calls record
  • 23:19 - 23:22
    dot save, assigns the id and returns the response.
  • 23:22 - 23:25
    So that's pretty straightforward. All that's
    really happened is,
  • 23:25 - 23:26
    and I really tried to do this and so
  • 23:26 - 23:28
    far I've been able to, I don't want to
  • 23:28 - 23:31
    have domain dot save. I want to have repository
  • 23:31 - 23:34
    dot save domain, so that there's no persistence
    that's
  • 23:34 - 23:38
    leaking into my domain objects. Persistence,
    I want to
  • 23:38 - 23:41
    be a secondary concern to what that object
    is
  • 23:41 - 23:42
    really doing.
  • 23:42 - 23:44
    And so far, it's worked, although sometimes
    it's caused
  • 23:44 - 23:48
    me some difficulty. The method messing down
    there is
  • 23:48 - 23:52
    kind of cool. At least I think it's cool.
  • 23:52 - 23:54
    And what it's doing is it's introducing a
    scope
  • 23:54 - 23:56
    object, which I'll show you in a, well here's
  • 23:56 - 23:58
    the scope object. That's OK.
  • 23:58 - 24:00
    And it creates a scope object, and this was
  • 24:00 - 24:02
    the trickiest code that I had to write, but
  • 24:02 - 24:04
    what it does, what it, the end result of
  • 24:04 - 24:07
    this code is, that allows you to chain call
  • 24:07 - 24:11
    any of your ActiveRecord find methods of your
    scopes
  • 24:11 - 24:13
    and chain them together. So you can now use,
  • 24:13 - 24:16
    so in other words, with this logic, wherever
    you
  • 24:16 - 24:22
    have like ActiveRecord dot, you know, find
    where id
  • 24:22 - 24:25
    greater than 122 is activated and so on, wherever
  • 24:25 - 24:26
    you might have a chain like that, you can
  • 24:26 - 24:29
    still use that chain now because of this scope
  • 24:29 - 24:30
    class with your domain objects.
  • 24:30 - 24:34
    Except, instead of getting back your ActiveRecord
    object, you're
  • 24:34 - 24:38
    getting back its domain representation. Does
    that make sense?
  • 24:38 - 24:39
    yeah?
  • 24:39 - 24:41
    OK. And then the mapper is what maps them
  • 24:41 - 24:44
    across, right. And some cases, the mapping
    is like
  • 24:44 - 24:47
    really, really simple. What's the next slide
    here? Oh,
  • 24:47 - 24:50
    yeah. Yeah. There it is right there.
  • 24:50 - 24:52
    So the mapper, because of the way vertice
    works
  • 24:52 - 24:55
    and the way ActiveRecord works with attributes,
    you can
  • 24:55 - 24:58
    almost just instantiate one from the other
    just passing
  • 24:58 - 25:00
    the attributes back and forth. So it's actually
    quite
  • 25:00 - 25:04
    easy except when it's not easy, and then what
  • 25:04 - 25:07
    happens, what I'm doing now is just, wherever
    I
  • 25:07 - 25:09
    have something that doesn't fit this model,
    I just
  • 25:09 - 25:12
    subclass this mapper with a custom mapper
    and override
  • 25:12 - 25:14
    those methods, more or less.
  • 25:14 - 25:17
    And that's the part that I think there, would
  • 25:17 - 25:20
    be more expressive ways to do that mapping
    and
  • 25:20 - 25:22
    that's what I'm starting to look at now. But
  • 25:22 - 25:25
    so far this has actually worked pretty well.
  • 25:25 - 25:28
    And it's allowed me to completely separate
    the way
  • 25:28 - 25:31
    I think about persistence from the way I think
  • 25:31 - 25:33
    about my domain object. SO we before we, before
  • 25:33 - 25:35
    I had this, we had, used to have an
  • 25:35 - 25:37
    order class, and I kid you not, and I'm
  • 25:37 - 25:40
    in, I'm a Rails noob so you can shoot
  • 25:40 - 25:45
    me, but it had forty-eight attributes in the
    ActiveRecord::Model,
  • 25:45 - 25:46
    right.
  • 25:46 - 25:48
    That is now represented by about eight classes
    that
  • 25:48 - 25:51
    separate out all the different aspects of
    the order,
  • 25:51 - 25:57
    like the shipping, the billing, et cetera,
    et cetera.
  • 25:57 - 25:59
    So that, but I think this mapping is, is,
  • 25:59 - 26:02
    is, is one of the more challenging parts.
    And
  • 26:02 - 26:05
    then finally, you now I've, then you quickly
    run
  • 26:05 - 26:10
    into things like, oh, well what happens if
    I
  • 26:10 - 26:12
    get the record. I get the record, I save
  • 26:12 - 26:14
    it, then I save it again. I have to
  • 26:14 - 26:15
    be able to keep track that I've saved it
  • 26:15 - 26:16
    once already.
  • 26:16 - 26:18
    So that I don't have multiple copies around.
    And
  • 26:18 - 26:20
    this is an identity map. In fact, an identity
  • 26:20 - 26:25
    map was built into Rails and I think it
  • 26:25 - 26:27
    might, I don't, I don't work in Rails 4,
  • 26:27 - 26:29
    but I think it might be taken out or,
  • 26:29 - 26:31
    or changed slightly. But actually, on the
    next slide
  • 26:31 - 26:33
    I'll show you what the identity map looks
    like,
  • 26:33 - 26:35
    and this, actually, I stole from Rails and
    made
  • 26:35 - 26:37
    it a bit simpler.
  • 26:37 - 26:38
    But all it's doing is just making sure that
  • 26:38 - 26:42
    there's a unique instance on, on a per-API
    request
  • 26:42 - 26:46
    call for each entity object. So that I, I
  • 26:46 - 26:48
    can, and it actually serves as a really cheap
  • 26:48 - 26:51
    cache, but that's not what it's intent is.
  • 26:51 - 26:54
    So, so that's kind of where, where we're,
    where
  • 26:54 - 26:57
    I'm going now, is most of those service object,
  • 26:57 - 26:59
    form object, request, those all kind of work
    well
  • 26:59 - 27:01
    for us, and I'm not looking at what would
  • 27:01 - 27:04
    a repository pattern look like fleshed out.
    And that
  • 27:04 - 27:06
    is, by far, the most challenging piece.
  • 27:06 - 27:09
    But I heard a question about where you put
  • 27:09 - 27:11
    things. You can really just put them anywhere.
    You
  • 27:11 - 27:14
    know. That's the thing, like, Rails just seems
    to
  • 27:14 - 27:18
    be, there's a part that's, I just felt constrained.
  • 27:18 - 27:21
    Like, Rails didn't give me any guidance on
    where
  • 27:21 - 27:22
    do I put a service socket. Well, you can
  • 27:22 - 27:24
    really put it anywhere you like. You could
    put
  • 27:24 - 27:26
    it on the auto load path or you can
  • 27:26 - 27:29
    put it where Rails might expect to see it.
  • 27:29 - 27:30
    But it, you can just create a services directory
  • 27:30 - 27:32
    and put your service objects there. Rails
    will find
  • 27:32 - 27:35
    it. It's not hard. But Rails doesn't really
    kind
  • 27:35 - 27:37
    of tell you what to do. So it doesn't
  • 27:37 - 27:39
    make it easy to do these, to think of
  • 27:39 - 27:42
    it, but it actually, it's not hard.
  • 27:42 - 27:45
    And but that's where we're going with that,
    and
  • 27:45 - 27:47
    we'll, we'll give you a link to it, to,
  • 27:47 - 27:49
    I've started a Git Repo where I plan to
  • 27:49 - 27:51
    share some of these ideas, and if you're interested
  • 27:51 - 27:53
    in sharing ideas with us on that, we'll, we'll
  • 27:53 - 27:58
    be happy to talk to you or, or join
  • 27:58 - 28:00
    us on the, on the Git Repo.
  • 28:00 - 28:05
    E.R.: Right. So what's the point, again? At
    the
  • 28:05 - 28:11
    start, we had these three things. Embrace
    complexity. Getting,
  • 28:11 - 28:15
    getting the solution to work is only part
    of
  • 28:15 - 28:18
    the fun, right? It's, it's your first draft,
    as
  • 28:18 - 28:20
    DHH said, talking about writing. You get,
    you do
  • 28:20 - 28:25
    it over and over. And getting things to work,
  • 28:25 - 28:28
    that ninety-line method I showed earlier,
    that worked. But
  • 28:28 - 28:31
    does anyone want to go back and use that
  • 28:31 - 28:34
    again? It's no, it's no fun to revisit, unless
  • 28:34 - 28:37
    you're improving it, which, I did do eventually
    and,
  • 28:37 - 28:41
    and now it's much nicer to work with. That,
  • 28:41 - 28:44
    that's fun. At least, we think that's fun.
    Getting
  • 28:44 - 28:47
    beyond the problem and getting it to a level
  • 28:47 - 28:49
    where we actually don't mind going into our
    code,
  • 28:49 - 28:53
    extending things, and changing things.
  • 28:53 - 28:55
    And we do that by breaking them up into
  • 28:55 - 28:58
    the smaller parts with the patterns that we've
    talked
  • 28:58 - 28:58
    about.
  • 28:58 - 29:01
    Knowing where you're going. In Alice in Wonderland,
    paraphrase,
  • 29:01 - 29:03
    they said if you don't know where you're going,
  • 29:03 - 29:07
    any road will take you there. We think it's
  • 29:07 - 29:08
    important to know where you're going and pick
    a
  • 29:08 - 29:10
    road that you think will take you there. It
  • 29:10 - 29:12
    doesn't have to be our road. These are things
  • 29:12 - 29:15
    that helped us, and, and we think they're
    good
  • 29:15 - 29:17
    ideas. But it's not gonna solve every problem
    for
  • 29:17 - 29:21
    everyone. These aren't rules. Like Declan
    said, they're not
  • 29:21 - 29:23
    science. You can't just take them and throw
    them
  • 29:23 - 29:25
    on and expect that your code will magically
    get
  • 29:25 - 29:28
    better by going on a diet.
  • 29:28 - 29:31
    And be more than just a Rails developer. Not
  • 29:31 - 29:34
    that it's bad to be a Rails developer, but
  • 29:34 - 29:41
    these things apply across, across languages
    and stuff. Like,
  • 29:41 - 29:45
    don't just learn Ruby. Learn, learn beyond
    that. So
  • 29:45 - 29:49
    as Declan mentioned, we have this GitHUb that
    we've
  • 29:49 - 29:52
    set up, and right now it's just a readme.
  • 29:52 - 29:55
    We don't actually have the code in their yet.
  • 29:55 - 29:56
    We'll likely put some of the code we showed
  • 29:56 - 29:59
    today, especially the repository bits.
  • 29:59 - 30:02
    But we'd love to continue the discussion via
    issues,
  • 30:02 - 30:06
    pull requests, whatever. That would be awesome.
    And, and
  • 30:06 - 30:07
    we'd love to talk to you about some of
  • 30:07 - 30:11
    it throughout this week as well. In fact,
    I'm
  • 30:11 - 30:15
    gonna also mentioned, we have reading. You'll
    be able
  • 30:15 - 30:18
    to get these slides after, so I'm just gonna
  • 30:18 - 30:19
    gloss over them.
  • 30:19 - 30:22
    These are a couple of the books that we've
  • 30:22 - 30:23
    taken a look at to help us out with
  • 30:23 - 30:24
    these things.
  • 30:24 - 30:26
    D.W.: I think the crowd has spoken and it's
  • 30:26 - 30:29
    time to wrap up. If you want to come
  • 30:29 - 30:31
    up and talk, I'm happy to answer your questions.
  • 30:31 - 30:33
    Thank you.
Title:
RailsConf 2014 - Domain Driven Design and Hexagonal Architecture with Rails
Description:

more » « less
Video Language:
English
Duration:
30:58

English subtitles

Revisions