Interfaces Not
Required
@plainprogrammer #rubyhack2018
Interfaces Not Required — RubyHACK 2018
What Are
Interfaces?
@plainprogrammer #rubyhack2018
What Good Are
Interfaces?
@plainprogrammer #rubyhack2018
@plainprogrammer #rubyhack2018
type BusinessRepository interface {
FindByID(businessID int) Business
CreateBusiness(business Business) bool
UpdateBusiness(business Business) bool
}
interface BusinessRepository {
Business findByID(int businessID);
bool createBusiness(Business business);
bool updateBusiness(Business business);
}
What Do We
Want From
Interfaces?
@plainprogrammer #rubyhack2018
Specifying Behavior
I want any Repository class to provide three methods:
• find_by_id
• create
• update
I want find_by_id to return either nil or an Entity for
that Repository.
I want create to return either an integer ID for the
created Entity, or false
I want update to return either an integer ID for the
updated Entity, or false
@plainprogrammer #rubyhack2018
Specifying Behavior
I want to be able to use the integer returned by create
with find_by_id to retrieve the Entity that has
matching attributes to the one I asked to be created.
I want to be able to use the integer returned by update
with find_by_id to retrieve the Entity that has
matching attributes to the one I asked to be updated.
@plainprogrammer #rubyhack2018
The Basics
RSpec.shared_examples "a Repository" do
subject { described_class }
let(:invalid_entity) { Object.new }
it { is_expected.to respond_to :find_by_id }
it { is_expected.to respond_to :create }
it { is_expected.to respond_to :update }
end
RSpec.describe BusinessRepository do
it_behaves_like "a Repostiory"
end
@plainprogrammer #rubyhack2018
Adding Some Safety
RSpec.shared_examples "a Repository" do
describe ".create" do
it "returns nil when provided an invalid entity" do
expect(subject.create invalid_entity).to be nil
end
it "returns an integer when provided a valid entity" do
expect(subject.create valid_entity).to be_a Integer
end
end
end
@plainprogrammer #rubyhack2018
Adding Some Safety
RSpec.describe BusinessRepository do
it_behaves_like "a Repostiory" do
let(:valid_entity) do
Business.new.tap do |business|
business.name = "Example Corp."
end
end
end
end
@plainprogrammer #rubyhack2018
Adding More Safety
RSpec.shared_examples "a Repository" do
describe ".update" do
it "returns nil when provided an invalid entity" do
expect(subject.update invalid_entity).to be nil
end
it "returns the entity's ID when provided a valid entity" do
valid_entity.instance_variable_set(:@id, 15)
expect(subject.update valid_entity).to eq 15
end
end
end
@plainprogrammer #rubyhack2018
Adding More Safety
RSpec.shared_examples "a Repository" do
describe ".find_by_id" do
context "with an unknown ID" do
it "returns nil" do
expect(subject.find_by_id 23).to eq nil
end
end
end
end
@plainprogrammer #rubyhack2018
Specifying Behavior
I want to be able to use the integer returned by create
with find_by_id to retrieve the Entity that has
matching attributes to the one I asked to be created.
I want to be able to use the integer returned by update
with find_by_id to retrieve the Entity that has
matching attributes to the one I asked to be updated.
@plainprogrammer #rubyhack2018
Adding More Safety
RSpec.shared_examples "a Repository" do
describe ".find_by_id" do
context "after an entity is created" do
it "returns the expected entity" do
entity_id = subject.create valid_entity
found_entity = subject.find_by_id entity_id
expect(found_entity.id).to eq entity_id
end
end
end
end
@plainprogrammer #rubyhack2018
Adding More Safety
RSpec.shared_examples "a Repository" do
describe ".find_by_id" do
context "after an entity is updated" do
it "returns the expected entity" do
entity_id = subject.create valid_entity
change_entity.call valid_entity
subject.update valid_entity
found_entity = subject.find_by_id entity_id
expect(found_entity).to eq valid_entity
end
end
end
end
@plainprogrammer #rubyhack2018
What Do We
Want From
Interfaces?
@plainprogrammer #rubyhack2018
Thanks!
I’m James Thompson
Staff Software Engineerfor Nav. Building software for the web since 2003,
using Rubysince 2006.
@plainprogrammer

More Related Content

PDF
Hilt Annotations
PDF
Intro to the Intersection Observer API - Tara Ojo
PDF
AI: Integrate Search Function into Your App Using Bing Search API.
PPTX
Rest api code completion for javascript - dotjs 2015
PPTX
Method and decorator
PPT
Understanding AJAX
PPTX
New features in C# 6
PDF
Using ReasonML For Your Next JavaScript Project
Hilt Annotations
Intro to the Intersection Observer API - Tara Ojo
AI: Integrate Search Function into Your App Using Bing Search API.
Rest api code completion for javascript - dotjs 2015
Method and decorator
Understanding AJAX
New features in C# 6
Using ReasonML For Your Next JavaScript Project

Similar to Interfaces Not Required — RubyHACK 2018 (20)

PDF
Controller Testing: You're Doing It Wrong
PDF
Introduction to Spring Boot.pdf
PDF
Constance et qualité du code dans une équipe - Rémi Prévost
PDF
Paying off technical debt with PHPSpec
PDF
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
PDF
Webauthn Tutorial
PPTX
Testing C# and ASP.net using Ruby
PDF
Joel Landis Net Portfolio
PDF
Unethical JavaScript - Giorgio Natili - Codemotion Rome 2017
PDF
Hands On: Create a Lightning Aura Component with force:RecordData
PPTX
Lerman Vvs14 Ef Tips And Tricks
PDF
"Ruby meets Event Sourcing" by Anton Paisov
PPT
Salesforce and sap integration
PDF
.NET Portfolio
PDF
Tips and tricks for building api heavy ruby on rails applications
PDF
Making Things Work Together
PDF
PHPUnit Episode iv.iii: Return of the tests
PPTX
How Reactive do we need to be
PPTX
Agile data presentation 3 - cambridge
PDF
Connecting to a Webservice.pdf
Controller Testing: You're Doing It Wrong
Introduction to Spring Boot.pdf
Constance et qualité du code dans une équipe - Rémi Prévost
Paying off technical debt with PHPSpec
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Webauthn Tutorial
Testing C# and ASP.net using Ruby
Joel Landis Net Portfolio
Unethical JavaScript - Giorgio Natili - Codemotion Rome 2017
Hands On: Create a Lightning Aura Component with force:RecordData
Lerman Vvs14 Ef Tips And Tricks
"Ruby meets Event Sourcing" by Anton Paisov
Salesforce and sap integration
.NET Portfolio
Tips and tricks for building api heavy ruby on rails applications
Making Things Work Together
PHPUnit Episode iv.iii: Return of the tests
How Reactive do we need to be
Agile data presentation 3 - cambridge
Connecting to a Webservice.pdf

More from James Thompson (15)

PDF
Bounded Contexts for Legacy Code
PDF
Beyond Accidental Arcitecture
PDF
Effective Pair Programming
PPTX
Wrapping an api with a ruby gem
PPTX
Microservices for the Monolith
PDF
Mocking & Stubbing
KEY
Learn Ruby 2011 - Session 5 - Looking for a Rescue
KEY
Learn Ruby 2011 - Session 4 - Objects, Oh My!
KEY
Learn Ruby 2011 - Session 3
KEY
Learn Ruby 2011 - Session 1
KEY
Learn Ruby 2011 - Session 2
KEY
Rails: Scaling Edition - Getting on Rails 3
KEY
Ruby For Web Development
KEY
Ruby Testing: Cucumber and RSpec
KEY
Introducing Ruby
Bounded Contexts for Legacy Code
Beyond Accidental Arcitecture
Effective Pair Programming
Wrapping an api with a ruby gem
Microservices for the Monolith
Mocking & Stubbing
Learn Ruby 2011 - Session 5 - Looking for a Rescue
Learn Ruby 2011 - Session 4 - Objects, Oh My!
Learn Ruby 2011 - Session 3
Learn Ruby 2011 - Session 1
Learn Ruby 2011 - Session 2
Rails: Scaling Edition - Getting on Rails 3
Ruby For Web Development
Ruby Testing: Cucumber and RSpec
Introducing Ruby

Recently uploaded (20)

PDF
Zenith AI: Advanced Artificial Intelligence
PDF
sustainability-14-14877-v2.pddhzftheheeeee
PPTX
Chapter 5: Probability Theory and Statistics
PDF
Hindi spoken digit analysis for native and non-native speakers
PPTX
Benefits of Physical activity for teenagers.pptx
PDF
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
PDF
ENT215_Completing-a-large-scale-migration-and-modernization-with-AWS.pdf
PDF
Taming the Chaos: How to Turn Unstructured Data into Decisions
PDF
Developing a website for English-speaking practice to English as a foreign la...
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Hybrid horned lizard optimization algorithm-aquila optimizer for DC motor
PDF
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
PDF
Five Habits of High-Impact Board Members
PDF
Enhancing emotion recognition model for a student engagement use case through...
PDF
August Patch Tuesday
PDF
A contest of sentiment analysis: k-nearest neighbor versus neural network
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PDF
Unlock new opportunities with location data.pdf
PDF
A Late Bloomer's Guide to GenAI: Ethics, Bias, and Effective Prompting - Boha...
PDF
WOOl fibre morphology and structure.pdf for textiles
Zenith AI: Advanced Artificial Intelligence
sustainability-14-14877-v2.pddhzftheheeeee
Chapter 5: Probability Theory and Statistics
Hindi spoken digit analysis for native and non-native speakers
Benefits of Physical activity for teenagers.pptx
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
ENT215_Completing-a-large-scale-migration-and-modernization-with-AWS.pdf
Taming the Chaos: How to Turn Unstructured Data into Decisions
Developing a website for English-speaking practice to English as a foreign la...
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Hybrid horned lizard optimization algorithm-aquila optimizer for DC motor
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
Five Habits of High-Impact Board Members
Enhancing emotion recognition model for a student engagement use case through...
August Patch Tuesday
A contest of sentiment analysis: k-nearest neighbor versus neural network
Group 1 Presentation -Planning and Decision Making .pptx
Unlock new opportunities with location data.pdf
A Late Bloomer's Guide to GenAI: Ethics, Bias, and Effective Prompting - Boha...
WOOl fibre morphology and structure.pdf for textiles

Interfaces Not Required — RubyHACK 2018

  • 5. @plainprogrammer #rubyhack2018 type BusinessRepository interface { FindByID(businessID int) Business CreateBusiness(business Business) bool UpdateBusiness(business Business) bool } interface BusinessRepository { Business findByID(int businessID); bool createBusiness(Business business); bool updateBusiness(Business business); }
  • 6. What Do We Want From Interfaces? @plainprogrammer #rubyhack2018
  • 7. Specifying Behavior I want any Repository class to provide three methods: • find_by_id • create • update I want find_by_id to return either nil or an Entity for that Repository. I want create to return either an integer ID for the created Entity, or false I want update to return either an integer ID for the updated Entity, or false @plainprogrammer #rubyhack2018
  • 8. Specifying Behavior I want to be able to use the integer returned by create with find_by_id to retrieve the Entity that has matching attributes to the one I asked to be created. I want to be able to use the integer returned by update with find_by_id to retrieve the Entity that has matching attributes to the one I asked to be updated. @plainprogrammer #rubyhack2018
  • 9. The Basics RSpec.shared_examples "a Repository" do subject { described_class } let(:invalid_entity) { Object.new } it { is_expected.to respond_to :find_by_id } it { is_expected.to respond_to :create } it { is_expected.to respond_to :update } end RSpec.describe BusinessRepository do it_behaves_like "a Repostiory" end @plainprogrammer #rubyhack2018
  • 10. Adding Some Safety RSpec.shared_examples "a Repository" do describe ".create" do it "returns nil when provided an invalid entity" do expect(subject.create invalid_entity).to be nil end it "returns an integer when provided a valid entity" do expect(subject.create valid_entity).to be_a Integer end end end @plainprogrammer #rubyhack2018
  • 11. Adding Some Safety RSpec.describe BusinessRepository do it_behaves_like "a Repostiory" do let(:valid_entity) do Business.new.tap do |business| business.name = "Example Corp." end end end end @plainprogrammer #rubyhack2018
  • 12. Adding More Safety RSpec.shared_examples "a Repository" do describe ".update" do it "returns nil when provided an invalid entity" do expect(subject.update invalid_entity).to be nil end it "returns the entity's ID when provided a valid entity" do valid_entity.instance_variable_set(:@id, 15) expect(subject.update valid_entity).to eq 15 end end end @plainprogrammer #rubyhack2018
  • 13. Adding More Safety RSpec.shared_examples "a Repository" do describe ".find_by_id" do context "with an unknown ID" do it "returns nil" do expect(subject.find_by_id 23).to eq nil end end end end @plainprogrammer #rubyhack2018
  • 14. Specifying Behavior I want to be able to use the integer returned by create with find_by_id to retrieve the Entity that has matching attributes to the one I asked to be created. I want to be able to use the integer returned by update with find_by_id to retrieve the Entity that has matching attributes to the one I asked to be updated. @plainprogrammer #rubyhack2018
  • 15. Adding More Safety RSpec.shared_examples "a Repository" do describe ".find_by_id" do context "after an entity is created" do it "returns the expected entity" do entity_id = subject.create valid_entity found_entity = subject.find_by_id entity_id expect(found_entity.id).to eq entity_id end end end end @plainprogrammer #rubyhack2018
  • 16. Adding More Safety RSpec.shared_examples "a Repository" do describe ".find_by_id" do context "after an entity is updated" do it "returns the expected entity" do entity_id = subject.create valid_entity change_entity.call valid_entity subject.update valid_entity found_entity = subject.find_by_id entity_id expect(found_entity).to eq valid_entity end end end end @plainprogrammer #rubyhack2018
  • 17. What Do We Want From Interfaces? @plainprogrammer #rubyhack2018
  • 18. Thanks! I’m James Thompson Staff Software Engineerfor Nav. Building software for the web since 2003, using Rubysince 2006. @plainprogrammer

Editor's Notes

  • #4: Interfaces are the intended means of interaction between different parts of a software system. Sometimes Interfaces are expressed formally, and enforced via compile-time checks. But, there are lots of places where Interfaces are much less formal. In Ruby we have no formal interfaces. There’s no language-level mechanism to tell us that some class will expose certain methods or attributes. And, while advocates of stronger typing think this is a detriment, I’m not convinced.
  • #5: Formal Interfaces provide a level of safety. They allow a for level of assurance to be given that classes that implement the same Interface are somewhat similar. But, in every formal Interface the guarantees are around type safety. Let’s take a look at two examples of such formal Interface definitions.
  • #6: So, looking at these we can ask what do we gain from either of these interfaces? We know that whatever implements these Interfaces will have to provide a set of three methods with specific signatures. They’ll be expected to take certain types of arguments and return certain types. And, that’s what we get. We get some type safety. So, that’s cool. But, there’s a lot more to most software than strict type safety. Type safety only protects against erroneous behavior arising from issues involving incorrect types. But, that constitutes potentially only the most basic facet of erroneous behavior within a software system. So, at least in my estimation, I don’t see the type of safety afforded by formal Interfaces to be that valuable.
  • #7: In more strongly typed languages, Interfaces act to extend the protections of the type system. In Ruby we can approximate these kinds of protections. But, if we want to enforce types our code can quickly become dense and unfriendly. When I think about Interfaces I don’t want just type safety, I want behavioral safety. I want to be able to specify that two classes behave the same way, and that goes beyond what formal interfaces afford. What I want from interfaces is to be able to describe what I think my duck should be able to do. So, let’s consider an example case.
  • #8: So, for our example case, let’s specify some behavior. I want to define a basic Repository interface that can be applied to any number of Repositories for different Entities. For those unfamiliar with the Repository Pattern, it is a way to abstract data access in a way such that your application works with simpler objects representing your business concerns, rather than more complex objects that know how to store and retrieve themselves via built-in understanding of the data access layer. The Repository Pattern is often used as an alternative to the Active Record, and Data Mapper patterns, which are all defined more thoroughly in the book Patterns of Enterprise Application Architecture. So, getting back to our example. The first thing we want is to define that a Repository in our system provides three methods. [CLICK] Find by ID, Create and Update. These are essentially the same three methods we were essentially working with in our earlier examples, except because we are in Ruby we can loosen up our Interface in ways that hopefully will demonstrates the benefits of our less formal approach. Second, [CLICK] we want Find By ID to return either nil or the Entity that our Repository is responsible for. Now we’re specifying some more flexible notions of behavior. We’re only going a little further than what our formal Interfaces afforded us, but the lack of their rigidity is going to show up again in how we specify the behavior of our other two methods. Third, [CLICK] we want the Create method to return either an integer ID for the created Entity, or false. And, finally [CLICK] we want the Update method to return either an integer ID for the updated Entity, or false. In these last two cases we are able to leverage Ruby’s flexible notion of truthiness to our advantage. But, our specification really only restates what the formal Interfaces provided but with Ruby’s flexibility brought into the picture. But, this is not the end of what behavior I want to specify for a Repository.
  • #9: I also want to specify that if I create an Entity via the Repository [CLICK] I can then use the returned Integer in Find By ID to get back the object with matching attributes to what I put in. And, [CLICK] I want essentially the same behavior to work with the Repository’s Update method. Now we have a mental model for our Interface, but how do we define such a thing? We’ve now gone well beyond what any formal Interface is going to provide us. So, how can we make our specification a reality? — That question’s not rhetorical… TESTS. We need to write tests. We can achieve the assurances of both formal Interfaces and our behavioral desires by converting these specifications into tests. Now, I am going to use RSpec to demonstrate this, you can do all this with MiniTest.
  • #10: So, here is the start of our Interface specification in RSpec. [CLICK] We’ve got three basic examples where we say that we expect the three methods to exist that we talked about earlier. We’re defining Subject for convenience since we want to call methods directly on the Repository class, rather than instantiating an instance. And, the Invalid Entity we setup will come up later. At this point we have a really rudimentary specification for our Interface. And we could use it like the following. [CLICK] We don’t have the same level of protection as afforded by a formal interface, but we can get there.
  • #11: Let’s add some additional safety for the Create method first. [CLICK] Now you can see where we are making use of the Invalid Entity we defined before. We expect that if we pass that to our Create method we will get back nil. Our implementation will now have to handle being given objects that are not the type they expect and to do something, in this case return nil. The internals could be more complicated, but for our example this will suffice. Additionally, we expect to get back an integer for the entity we just created when a Valid Entity is supplied. But, where does this Valid Entity come from? We haven’t defined it anywhere yet, and this is where I think RSpec’s shared examples demonstrate some particular elegance. We can update our Business Repository spec to supply what the shared examples need.
  • #12: [CLICK] We can nest a Let call that defines the additional context our shared examples need, in this case a valid instance of the Business class. We’ll leverage more of this capability in a bit. But, now we have some more safety in our specification. We can now trust that when Business Repository receives a Valid Entity to the Create method it will return an integer value, and if it receives an Invalid Entity it will return nil. We could even define a repository specific Invalid Entity alongside our Valid Entity to better describe what being Invalid means in context.
  • #13: Now we can add similar safety checks to our Update method. [CLICK] Again, we’re relying on our definitions of a Valid and Invalid Entity to specify how we want our repositories to behave. For update we want to ensure that the returned integer value from our Update method matches the ID value already set on the Entity that is being updated. Now, we’re reaching inside the Entity to set the ID in a way that is not desirable, but for our example it keeps things simple. The last bit of safety checks we need is around Find By ID.
  • #14: [CLICK] This example does not address type constraints, but we certainly could. In cases like this I would tend to go with an implementation that relies on type coercion so that anything that can have TO_I called on it is acceptable. But, this covers the behavior we want. So, now we have roughly equivalent safety checks to what a formal Interface gives us, and we’re ready to address what Interfaces can not. So, let’s review the behavioral specifications we have not covered.
  • #15: We want to be sure that the integer values that come back from the Create and Update methods can be used with Find By ID to properly retrieve the expected Entity. So, our next sets of tests are going to address the interaction between the Create and Update methods and how Find By ID is expected to work.
  • #16: First, we will address Find By ID’s behavior after entity creation. [CLICK] We invoke the Create method with our Valid Entity and capture the returned ID for that entity. Then we call Find By ID with that ID and store off the returned entity. Then we assert that the ID of the entity we found matches the ID that was returned by the Create method. We can’t compare the Found Entity to the Valid Entity because we are not assuming that the Valid Entity will be modified. This gives us assurance that formal Interfaces can not. We now have behavioral characteristics that are important to what our interface does and means, not just what the defined methods accept and return. This is why I don’t think formal interfaces are a necessary language feature for Ruby. We can achieve more robust results through good testing. We don’t need to embrace the rigidity of a stronger type system to be safe, and through robust testing we can weave the concerns over type into our behavioral specifications, which are richer and more expressive anyways. But, let’s finish up the example by looking at the relationship between Find By ID and the Update method.
  • #17: In our final example [CLICK] we go through the dance of creating an entity, calling a lambda our more specific context will have to provide, updating our entity in the repository and then performing a check against what Find By ID returns and the entity we modified. The change entity lambda can be supplied to the shared example in the same manned as the valid entity, so we won’t look at it. But, again, we are able to encapsulate a robust test of our interfaces important behavior. We could also add more granular checks to ensure stricter typing for what the Find By ID method returns. But, given the interplay between the context the shared example is used in and the tests in the shared example the value would come from only from verbosity, not from additional assurances. With that I want to return to a question I posed earlier.
  • #18: What do we want from interfaces? If all we want is type safety, we can get that from tests. And, if we focus our tests around behavior, we can actually achieve a richer model and more meaningful specifications around our informal interfaces than any formal interface is able to provide. This is some of why I still love Ruby after over 12 years of using it. I don’t need to craft my code for the ease of the compiler. I can achieve as much safety as I need, and define as much flexibility as I want through specifying behavior as tests. I don’t need the strictures of a strongly typed language, or the formality of interfaces to ensure interoperability. I can ensure more than what those mechanisms afford by writing what amounts to executable documentation.
  • #19: Thank you for being here at RubyHACK and for coming to my talk. Here are details on me. And, I’m happy to take any questions.