Estrategias de Organización: Paquete por Funcionalidad
Agrupa el código por funcionalidad de dominio — los paquetes de nivel superior revelan el negocio, cada funcionalidad está co-ubicada y la búsqueda mejora drásticamente a medida que el sistema crece.
Por qué importa
Package by Feature organiza el código de modo que la estructura de directorios revela qué hace el sistema. En lugar de controllers/, services/, repositories/, el nivel superior dice orders/, users/, payments/. Esto es lo que Robert Martin llama Arquitectura que Grita — la estructura anuncia el dominio de negocio, no la pila tecnológica.
Ventajas: Todo el código relacionado con Pedidos vive junto. Encontrar "¿cómo funciona el descuento de pedidos?" significa abrir un directorio en lugar de tres. Eliminar una funcionalidad significa eliminar un directorio. Para un nuevo desarrollador: "¿dónde vive el checkout?" — en checkout/.
La limitación: Package by Feature mejora la detectabilidad pero no aplica fronteras. Dentro de orders/, un controlador aún puede importar directamente un repositorio de users/. Los paquetes de funcionalidades pueden enredarse entre sí a través de importaciones ocultas. La estructura es mejor, pero la disciplina debe agregarse por separado — a través de modificadores de acceso, archivos barrel o puertos explícitos.
✗El problema
Feature packages exist but boundaries are violated. orders/ reaches into users/ internals, bypassing the public API and creating hidden cross-feature coupling.
Bad
# orders/order_controller.py
from orders.order_service import OrderService
from users.user_repository import UserRepository # ← crosses feature boundary!
class OrderController:
def place_order(self):
data = request.json
user_id = data["user_id"]
# Reaches INTO the users/ package internals — bypasses UserService
user_repo = UserRepository()
user = user_repo.find_by_id(user_id)
shipping_address = user.shipping_address # raw entity from another feature
return jsonify(OrderService().place(data, shipping_address))
# UserRepository is now coupled to OrderController.
# Refactoring UserRepository breaks OrderController — invisible dependency.
// orders/OrderController.ts
import { UserRepository } from "../users/UserRepository"; // ← crosses boundary!
import { OrderService } from "./OrderService";
export class OrderController {
async placeOrder(req: { body: { userId: string } }): Promise
✓La solución
Feature packages expose only a public service API. Internals (like UserRepository) are never exported outside the package. Cross-feature communication uses the public API only.
Good
# users/user_service.py ← public API of the users/ feature
from dataclasses import dataclass
from users._user_repository import UserRepository # private, underscore prefix
@dataclass
class ShippingAddress:
street: str
city: str
country: str
class UserService:
def get_shipping_address(self, user_id: str) -> ShippingAddress:
repo = UserRepository()
user = repo.find_by_id(user_id)
return ShippingAddress(street=user.street, city=user.city, country=user.country)
# orders/order_controller.py ← uses only the public API
from users.user_service import UserService # public API only
from orders.order_service import OrderService
class OrderController:
def place_order(self):
data = request.json
address = UserService().get_shipping_address(data["user_id"])
return jsonify(OrderService().place(data, address))
# UserRepository can be refactored freely — OrderController never touches it.
// users/UserService.ts ← public API of the users/ feature
export interface ShippingAddress { street: string; city: string; country: string; }
export class UserService {
async getShippingAddress(userId: string): Promise {
const repo = new UserRepository(); // internal
const user = await repo.findById(userId);
return { street: user.street, city: user.city, country: user.country };
}
}
// users/index.ts ← barrel: only exports public API
export { UserService } from "./UserService";
export type { ShippingAddress } from "./UserService";
// UserRepository is NOT exported
// orders/OrderController.ts ← uses only the barrel (public API)
import { UserService } from "../users"; // barrel import only
import { OrderService } from "./OrderService";
export class OrderController {
async placeOrder(req: { body: { userId: string } }): Promise
💡Conclusión clave
Package by Feature mejora la detectabilidad pero no aplica fronteras por sí solo. Necesitas modificadores de acceso o archivos barrel explícitos para evitar que las funcionalidades alcancen los internos de las demás. La estructura te dice dónde están las cosas; el control de acceso te dice qué puede llamar a qué.
🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.
Pista: Paquete por Funcionalidad mejora la descubribilidad pero no impone límites. Necesitas modificadores de acceso o puertos explícitos para evitar que las funcionalidades accedan a las partes internas de otras.
✗ Tu versión