Ignoring the clickbait-y title for a moment, I generally agree with the YAGNI principle. I want to share what I believe is an exception to that rule - that is, a situation where doing some upfront work even if you don’t immediately need it will pay off in the long run.
Specifically, if you’re building services in an OOP language that expose a public1 API, I strongly recommend creating separate classes for over-the-wire representations of your API entities and your business logic entities. In architect-speak, separate your Domain Model from your Data Transfer Objects.
In my experience, not doing this leads to the issues detailed below.
Most applications use libraries to serialize objects to an over-the-wire representation like JSON. Jackson is one such popular library for Java. Jackson does quite a bit of behind-the-scenes “magic” to free you from writing boilerplate. As such, it’s not always intuitive what class properties are serialized. For example, a private field becomes serializable by default if you add a getter for it!
To demonstrate, let’s suppose you’re building a dating app that offers an API and you have a
Instances of this class are serialized to JSON and sent over the wire in API responses. Suppose your app also calculates an internal “attractiveness score” for match-making purposes. If you’re using the same class for this so-called “business” logic, you might be tempted to add extra behavior like so:
Congrats! You have now ruined the self-esteem of your unsuspecting users! This is a hypothetical example, but if you’re working with any sort of security-sensitive or personal data, the potential ramifications are huge.
Maintainability issues arise from a lack of separation of concerns between the business and presentation layers. For example, if you want a
snake_case naming strategy
for your API fields, you might add a
JsonNaming(SnakeCaseStrategy.class) annotation to the class. Now, if you want to factor out
your business logic into a separate library (for code reuse), you have to pull in the
jackson-annotations dependency for absolutely
no reason. Another example is Swagger annotations - they should strictly live in the presentation layer.
For any long-term API, versioning is inevitable. Your software is always evolving, which means adding new functionality, fixing broken functionality, renaming fields etc, sometimes in a backwards-incompatible way. At this point, you would have to create new classes for the new version anyway, or even worse, add crufty branching code that juggles data based on versions.
If you’re building a medium-to-large application with an API contract, front-load the pain and create separate classed for your API presentation layer and business layer. PS: there are tools to ease this pain, but I haven’t personally used them.
I’d argue even if the only consumer of your APIs is your own frontend/mobile app, it’s still a public API ↩