DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • What Java DAO Layer Is Best for Your Project
  • Spring Microservice Tip: Abstracting the Database Hostname With Environment Variable
  • A Beginner's Guide to Back-End Development
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Trending

  • Teradata Performance and Skew Prevention Tips
  • A Guide to Container Runtimes
  • Contextual AI Integration for Agile Product Teams
  • How to Format Articles for DZone
  1. DZone
  2. Coding
  3. Frameworks
  4. Is OOP Compatible With an Enterprise Context?

Is OOP Compatible With an Enterprise Context?

Let's use Spring, Java, and Kotlin to see if OOP really holds up at the enterprise level and the various tradeoffs you'll have to consider when working on a project.

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Updated Jan. 14, 21 · Opinion
Likes (14)
Comment
Save
Tweet
Share
12.8K Views

Join the DZone community and get the full member experience.

Join For Free

This week, during a workshop related to a Java course I give at a higher education school, I noticed the code produced by the students was mostly ok — entirely procedural. In fact, though the Java language touts itself as an Object-Oriented language, it’s not uncommon to find such code developed by professional developers in enterprises. For example, the JavaBean specification is in direct contradiction of one of OOP’s main principles, encapsulation.

Another example is the widespread controller, service, and DAO architecture found equally in Java EE and Spring applications. In that context, entities are generally anemic, while all business logic is located in the service layer. While this is not bad per se, this design separates between state and behavior and sits at the opposite end of true OOP.

Both Java EE and the Spring framework enforce this layered design. For example, in Spring, there’s one annotation for every such layer: @Controller, @Service, and @Repository. In the Java EE world, only @EJB instances — the service layer — can be made transactional.

This post aims to try to reconcile both the OOP paradigm and the layered architecture. I’ll be using the Spring framework to highlight my point because I’m more familiar with it, but I believe the same approach could be used for pure Java EE apps.

A Simple Use Case

Let’s have a simple use-case: from an IBAN number, find the associated account with the relevant balance. Within a standard design, this could look like:

@RestController
class ClassicAccountController(private val service: AccountService) {

    @GetMapping("/classicaccount/{iban}")
    fun getAccount(@PathVariable("iban") iban: String) = service.findAccount(iban)
}

@Service
class AccountService(private val repository: ClassicAccountRepository) {
    fun findAccount(iban: String) = repository.findOne(iban)
}

interface ClassicAccountRepository : CrudRepository<ClassicAccount, String>

@Entity
@Table(name = "ACCOUNT")
class ClassicAccount(@Id var iban: String = "", var balance: BigDecimal = BigDecimal.ZERO)


There are a couple of issues there:

  1. The JPA specifications mandates for a no-arg constructor. Hence, it’s possible to create ClassicalAccount instances with an empty IBAN.
  2. There’s no validation of the IBAN. A full round-trip to the database is required to check if an IBAN is valid.

Note: No, there’s no currency. It’s a simple example, remember?

Being Compliant

In order to comply with the no-args constructor JPA constraint — and because we use Kotlin — it’s possible to generate a synthetic constructor. That means the constructor is accessible through reflection, but not by calling the constructor directly.

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <configuration>
        <compilerPlugins>
            <plugin>jpa</plugin>
        </compilerPlugins>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-noarg</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>


Note: If you use Java, tough luck, I don’t know of any option to fix that.

Adding Validation

In a layer architecture, the service layer is the obvious place to put the business logic, including validation:

@Service
class AccountService(private val repository: ClassicAccountRepository) {
    fun findAccount(iban: String): Account? {
        checkIban(iban)
        return repository.findOne(iban)
    }

    fun checkIban(iban: String) {
        if (iban.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")
    }
}


In order to be more OOP-compliant, we must decide whether we should allow invalid IBAN numbers or not. It’s easier to forbid it altogether.

@Entity
@Table(name = "ACCOUNT")
class OopAccount(@Id var iban: String, var balance: BigDecimal = BigDecimal.ZERO) {
    init {
        if (iban.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")
    }
}


However, this means that we must first create the OopAccount instance to validate the IBAN — with a balance of 0, even if the balance is actually not 0. Again, as per the empty IBAN, the code does not match the model. Even worse, to use the repository, we must access the OopAccount inner state:

repository.findOne(OopAccount(iban).iban)


A More OOP-Friendly Design

Improving the state of the code requires a major rework on the class model, separating between the IBAN and the account, so that the former can be validated and can access the latter. The IBAN class serves both as the entrypoint and the PK of the account.

@Entity
@Table(name = "ACCOUNT")
class OopAccount(@EmbeddedId var iban: Iban, var balance: BigDecimal)

class Iban(@Column(name = "iban") val number: String,
           @Transient private val repository: OopAccountRepository) : Serializable {

    init {
        if (number.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")
    }

    val account
        @JsonIgnore
        get() = repository.findOne(this)
}


Notice the returned JSON structure will be different from the one returned above. If that’s an issue, it’s quite easy to customize Jackson to obtain the desired result.

With this new design, the controller requires a bit of change:

@RestController
class OopAccountController(private val repository: OopAccountRepository) {

    @GetMapping("/oopaccount/{iban}")
    fun getAccount(@PathVariable("iban") number: String): OopAccount {
        val iban = Iban(number, repository)
        return iban.account
    }
}


The only disadvantage of this approach is that the repository needs to be injected into the controller, then be explicitly passed to the entity’s constructor.

The Final Touch

It would be great if the repository could automatically be injected into the entity when it’s created. Well, Spring makes it possible — though this is not a very well-known feature — through Aspect-Oriented Programming. It requires the following steps:

Add AOP Capabilities to the Application

To effectively add the AOP dependency is quite straightforward and requires just adding the relevant starter dependency to the POM:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>


Then, the application must be configured to make use of it:

@SpringBootApplication
@EnableSpringConfigured
class OopspringApplication


Update the Entity

  1. The entity must first be set as a target for injection. Dependency injection will be done through autowiring.
  2. Then, the repository must be moved from a constructor argument to a field.
  3. Finally, the database fetching logic can be moved into the entity:
    @Configurable(autowire = Autowire.BY_TYPE)
    class Iban(@Column(name = "iban") val number: String) : Serializable {
    
        @Transient
        @Autowired
        private lateinit var repository: OopAccountRepository
    
        init {
            if (number.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")
        }
    
        val account
            @JsonIgnore
            get() = repository.findOne(this)
    }

Note: Remember that field-injection is evil.

Aspect Weaving

There are two ways to weave the aspect, either compile-time weaving or load-time weaving. I choose the latter, as it is much easier to configure. It’s achieved through a standard Java agent.

  1. First, it needs to be added as a runtime dependency in the POM:
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-agent</artifactId>
        <version>2.5.6</version>
        <scope>runtime</scope>
    </dependency>
  2. Then, the Spring Boot plugin must be configured with the agent:
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <agent>${settings.localRepository}/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar</agent>
        </configuration>
    </plugin>
  3. Finally, the application must be configured accordingly:
    @EnableLoadTimeWeaving
    class OopspringApplication

And Then?

Of course, this sample leaves out an important piece of the design: How to update the balance of an account. The layered approach would have a setter for that, but that’s not OOP. Thinking about it, the balance of an account changes because there’s a transfer from another account. This could be modeled as:

fun OopAccount.transfer(source: OopAccount, amount: BigDecimal) { ... }


Experienced developers should see some transaction management requirements sneaking in. I leave the implementation to motivated readers. The next step would be to cache the values because accessing the database for each read and write would be killing performance.

Conclusion

There are a couple of points I’d like to make.

First, the answer to the title question is a resounding 'yes'. The results are true OOP code while still using a so-called enterprise-grade framework — namely Spring.

However, migrating to this OOP-compatible design came with a bit of overhead. Not only did we rely on field injection, we had to bring in AOP with load-time weaving. The first is a hindrance during unit testing, the second is a technology you definitely don’t want on every team, as they make apps more complex. And that’s only for a trivial example.

Finally, this approach has a huge drawback: most developers are not familiar with it. Whatever its advantages, they first must be "conditioned" to have this mindset. And that might be a reason to continue using the traditional layered architecture.

I’ve searched Google for scientific studies proving OOP is better in terms of readability and maintainability: I found none. I would be very grateful for pointers.

The complete source code for this post can be found on GitHub in Maven format.

Object-oriented programming Spring Framework Database Java EE Java (programming language) IT

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • What Java DAO Layer Is Best for Your Project
  • Spring Microservice Tip: Abstracting the Database Hostname With Environment Variable
  • A Beginner's Guide to Back-End Development
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!