---
title: Separate Data Mapping From Controllers
impact: MEDIUM
impactDescription: keeps controllers thin, focused on HTTP concerns, and makes mapping logic reusable
tags: controller, parsing, transformation, mapping, patterns, quality, kotlin
---

## Separate Data Mapping From Controllers

Controllers should be thin and only responsible for handling HTTP requests, orchestrating service calls, and returning responses. Complex data transformations or mapping logic should be extracted into separate Mapper classes or Extension functions.

**Incorrect (mapping logic in controller):**

```kotlin
@RestController
class UserController(private val userService: UserService) {
    
    @GetMapping("/users/{id}")
    fun getUser(@PathVariable id: String): ResponseEntity<UserResponse> {
        val user = userService.findById(id) ?: throw NotFoundException()
        
        // Complex mapping logic inside controller
        val response = UserResponse(
            id = user.id,
            fullName = "${user.firstName} ${user.lastName}",
            email = user.email.lowercase(),
            formattedDate = user.createdAt.format(DateTimeFormatter.ISO_DATE)
        )
        
        return ResponseEntity.ok(response)
    }
}
```

**Correct (using Mappers or Extension Functions):**

```kotlin
// Option 1: Using a dedicated Mapper
object UserMapper {
    fun toResponse(user: User): UserResponse = UserResponse(
        id = user.id,
        fullName = "${user.firstName} ${user.lastName}",
        email = user.email.lowercase(),
        formattedDate = user.createdAt.format(DateTimeFormatter.ISO_DATE)
    )
}

// Option 2: Using Kotlin Extension Functions
fun User.toResponse(): UserResponse = UserResponse(
    id = id,
    fullName = "$firstName $lastName",
    email = email.lowercase(),
    formattedDate = createdAt.format(DateTimeFormatter.ISO_DATE)
)

// Clean and focused controller
@RestController
class UserController(private val userService: UserService) {
    @GetMapping("/users/{id}")
    fun getUser(@PathVariable id: String): UserResponse {
        return userService.findById(id)?.toResponse() ?: throw NotFoundException()
    }
}
```

**Benefits:**
- **Reusability:** The same mapping logic can be used across multiple controllers or background jobs.
- **Testability:** Mappers can be unit-tested in isolation without mocking HTTP infrastructure.
- **Maintainability:** Changes to API formats are centralized.

**Tools:** MapStruct, Kotlin Extension Functions, ModelMapper, Architectural Review
