4

When programming in Spring/Spring Boot, it's a common practice to have every service/component implement an interface, for example:

public interface IAdder {
    int add(int a, int b);
}

@Service
public class Adder implements IAdder {
    public int add(int a, int b) { return a + b; }
}

Why do I actually need to write that interface if I don't plan on having multiple implementations for "Adder" functionality?

One common answer to that question is "you'll need it for mocking in your unit tests". To that, I can answer - not really. I can mock the adder using every possible mocking framework you can think of (Mockito, for example):

@Autowired
IMultiplier multiplier;  // Class under test

@Mock
IAdder adder;

@Test  // Assuming I implement multiplication by multiple additions
public void multiplicationTest() {
    // Arrange
    when(adder).add(3, 3).thenReturn(6);
    
    // Act
    int result = multiplier.multiply(3, 5);
    
    // Assert
    verify(adder, times(4)).add(3, 3);
    assertThat(result).isEqualTo(15);
}

Having said that, what advantage can the IAdder interface provide for me?

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
KidCrippler
  • 1,633
  • 2
  • 19
  • 34
  • ever heard of "programming to an interface"? – Stultuske Apr 21 '21 at 13:42
  • 1
    @Stultuske I sure have heard about the concept, but I'm trying to apply some critical thinking here and challenge a paradigm I don't see any use for. – KidCrippler Apr 21 '21 at 13:47
  • It has to do with how the proxy classes are autogenerated. – xdhmoore Apr 21 '21 at 13:49
  • @xdhmoore 10x, can you please elaborate? – KidCrippler Apr 21 '21 at 13:50
  • Had to look it up. This is the simple version: https://stackoverflow.com/questions/10664182/what-is-the-difference-between-jdk-dynamic-proxy-and-cglib#10664208 – xdhmoore Apr 21 '21 at 13:52
  • 1
    There isn't always an immediate huge advantage, although there are almost always incremental advantages, one of which is testing/DI/IoC/etc. In Spring Boot interfaces are used for proxy generation--that doesn't mean that *everything* needs a proxy; it depends on how it's being used (and used by the framework). Unrelated, but I'd name the interface `Adder` and name implementations based on their details. The whole `I` prefix rightfully died in Java some time ago. – Dave Newton Apr 21 '21 at 13:56
  • This also seems relevant. https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/core.html#aop-pfb-proxy-types – xdhmoore Apr 21 '21 at 13:56
  • 1
    "JDK dynamic proxies are preferred whenever you have a choice" according to that link. Anybody has a clue why this is the case? – Wim Deblauwe Apr 21 '21 at 14:00
  • 1
    @DaveNewton "TESTING" in all caps please. This makes testing with Spring a breeze. After coding for 15 years and being a committer in spring occasionally, this is my number one choice. – Eugene Apr 21 '21 at 15:08
  • 1
    While interfaces can be useful, I think this question is asking about an interface on *every* service-layer class. – xdhmoore Apr 21 '21 at 18:41

3 Answers3

4

TBH I hate it when people blindly follow the 'every service needs an interface' rule. It's a maintenance nightmare.

This kind of interfaces only make sense at module boundaries, where the interface itself resides in the consumer module, shouting 'hey, here's the interface I'm gonna need', and the provider module happily obliges.

Within a cohesive module, unless you're planning on multiple implementations of the same functionality, such interfaces make no sense at all. And, since you can easily convert a class into an interface without the clients complaining if you need to, I don't see why you should prematurely clutter your codebase with extra levels of indirection.

crizzis
  • 9,978
  • 2
  • 28
  • 47
1

I've had this same conversation with coworkers. I feel like this question is a little beyond my Java knowledge, but here's a shot:

I think there are 2 reasons people do it:

1. Java dynamic proxying

The way Spring honors some of the annotations like @Transaction is to create a proxy for the class.

According to the docs, Java dynamic proxying is default proxy-generation method over CGLIB-based proxies. Java dynamic proxying can only work with interfaces, so people got into the habit of creating interfaces for every services. Why is Java dynamic proxying preferred? I don't know, tbh, but these are some points:

  • CGLIB used to be a separate library you had to include. Nowadays it's included with Spring
  • CGLIB is a 3rd-party library to "hack" bytecode, vs the JDK-included dynamic proxying
  • I guess at some point constructors were invoked twice when using CGLIB? Maybe there are other quirks?
  • I have only a tiny bit of experience here, but I know CGLIB can give some opaque error messages sometimes.

I don't have enough experience to recommend going full-CGLIB just for the sake of removing redundant interfaces.

2. "Software engineering reasons", encouraged by the docs

To quote the docs:

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces.

Here the docs sort of indirectly encourage people to use interfaces. This seems to me to be a little heavy-handed, but it may be part of why it has become common practice. And once it's common practice, it becomes habit.

Conclusion

While I disagree with the idea that all service classes with a single implementation need a redundant interface, I agree that service classes should be treated as a "facade-point" in the code, encapsulating the underlying layers and providing a clean abstraction to the view layer. But I don't see why they need to have literal java interfaces unless the need arises.

That's what I think from a structural point of view. But without more CGLIB vs JDK-proxy info, I'll probably stick with the defaults.

xdhmoore
  • 8,935
  • 11
  • 47
  • 90
0

If you do not plan several options for implementing the service, then in theory you can do without the interface.

But, if you use unit tests, things are much more complicated. It is not always possible to use a working implementation in unit tests (for example, if you are using a database connection or interacting with an external application).

Therefore, it is better to use the interface. In your question, you gave the simplest case in which you can do without mock objects. But this is not always possible.

Streletz
  • 192
  • 3
  • 15
  • `It is not always possible to use a working implementation in unit tests` - why not? I can encapsulate whatever I want (be it a database connection or any kind of external interface) behind a service/component, just without the interface super-class. What am I missing? – KidCrippler Apr 21 '21 at 14:11