Note
Problem Decomposition Through a Recommendation System
What an outfit recommendation example revealed about decomposition, data modelling, scoring, feedback, personalization, and service architecture.
Purpose
Document what I learned by breaking down a seemingly simple problem: recommending what someone should wear today.
The exercise showed how a vague product idea can be decomposed into smaller problems, data structures, business rules, and eventually a recommendation system.
Key Idea
Complex software problems become easier to reason about when they are decomposed into smaller decisions with clear inputs, data, constraints, outputs, and ownership boundaries.
The goal is not to start with code. The goal is to first understand what the system actually needs to know, decide, and delegate.
Explanation
The original problem was simple:
Recommend what someone should wear today.
Breaking it down exposed several smaller problems:
- Categorize available clothing.
- Check weather conditions.
- Determine the occasion, such as work, travel, or a gathering.
- Filter out unsuitable clothing.
- Generate valid outfit combinations.
- Track previously worn outfits.
- Score the remaining combinations.
- Recommend the strongest option.
This produces a useful flow:
Inputs → Filtering → Combination Generation → Historical Constraints → Scoring → Recommendation
Data Modelling
The exercise also showed that application logic depends on having a clear data model.
A ClothingItem represents what an item is and can contain attributes such as type, colour, formality, warmth, and rain suitability.
An Outfit represents a specific combination of clothing items.
Because one outfit contains many clothing items and one clothing item can appear in many outfits, this is a many-to-many relationship. A junction structure such as OutfitItem can connect them.
WearHistory records what happened over time. Instead of storing a lastWornDate directly on a clothing item, the system can derive that information from historical records.
This reinforced an important distinction:
ClothingItem= what something isWearHistory= what happenedlast worn= information derived from history
Historical records also need to remain accurate. If an outfit changes after it has been worn, the historical record could become incorrect. A cleaner approach is to treat a changed combination as a new outfit or version rather than modifying the historical one.
Business Rules and Scoring
A valid outfit is not automatically the best outfit.
For example, on a cold rainy workday, the system may consider:
- rain protection
- cold protection
- work suitability
- user preferences
- how recently the outfit was worn
- style compatibility
Not every factor should have the same importance in every situation. Rain protection matters much more when it is raining than when it is sunny.
This leads to weighted scoring, where context changes the importance of different criteria.
Rules and Machine Learning
A hybrid approach makes more sense than using machine learning for everything.
Known constraints can remain deterministic:
- weather suitability
- required dress code
- clothing availability
Personalization can come from user preferences and observed behaviour.
Useful feedback signals include:
- like or dislike
- outfit rating
- comments
- whether the recommendation was accepted
- whether one item was replaced
- what alternative was actually worn
Behaviour can reveal preferences that the user never explicitly stated.
For example, if someone usually replaces formal shoes with sneakers for normal workdays but always chooses formal shoes for important meetings, the system should learn a context-dependent preference rather than a single global rule.
Exploration vs. Exploitation
Personalization creates another problem: if the system always recommends what it already knows the user likes, recommendations can become repetitive.
This is the exploration-versus-exploitation tradeoff:
- Exploitation: recommend combinations already known to work.
- Exploration: occasionally introduce new combinations to discover additional preferences.
The balance should also depend on context. An important client meeting may favour a proven recommendation, while a low-stakes casual day can tolerate more experimentation.
Context Acquisition and Confidence
The recommendation flow should begin by gathering context from the most reliable available sources.
Examples include:
- linked calendar for meetings, events, or travel
- weather data for temperature and precipitation
- time and date for season or weekday context
- saved user preferences
- direct user questions when important context is still missing
A useful design rule is:
Infer when confidence is high. Ask when uncertainty could materially change the decision.
For example, a calendar entry called Wedding Reception may provide enough context on its own, while an entry called Dinner may require a follow-up question.
Hard Constraints Before Ranking
The system should not immediately score every possible outfit.
It should first remove combinations that are not acceptable.
This creates a two-stage pipeline:
- Filtering
- remove weather-inappropriate items
- remove items that violate dress-code requirements
- remove dirty or unavailable items
- remove invalid combinations
- apply required repetition constraints
- Ranking
- score remaining combinations
- apply user preferences
- apply context-specific priorities
- consider history and novelty
- return the strongest candidates
This reinforced another rule:
Hard constraints first, soft preferences second.
Graceful Degradation
Filtering may sometimes leave no perfect recommendation.
Instead of failing, the system can relax soft constraints in a deliberate order while preserving hard constraints.
For example:
- Preserve required weather and safety constraints.
- Preserve explicit user hard constraints.
- Relax repetition avoidance.
- Relax style preferences.
- Relax formality only when the context allows it.
- Return the best available option and explain the compromise.
This is graceful degradation: the system remains useful even when ideal conditions are unavailable.
Modules and Services
The recommendation system can be divided into modules based on responsibility.
Possible modules include:
Wardrobefor clothing inventory and current item stateContextfor weather, calendar, and occasion informationRecommendationfor filtering, scoring, ranking, and fallback logicHistoryfor worn outfits and feedback eventsPreferencefor explicit and learned user preferences
A useful mental model is:
Module = a boundary around a responsibility.
Service = business logic that performs work inside or across those boundaries.
Examples:
ContextService
- get weather
- get calendar events
- determine occasion
- identify missing context
WardrobeService
- add or update clothing items
- categorize items
- get available items
- mark items clean or dirty
HistoryService
- record an outfit being worn
- record ratings or comments
- find recently worn items
- calculate last-worn information
PreferenceService
- get user preferences
- update explicit preferences
- update preferences from feedback
- expose constraint priorities
RecommendationService
- filter invalid outfits
- calculate outfit scores
- rank candidates
- relax soft constraints
- generate recommendations
The RecommendationService can orchestrate the other services without owning their data.
Separation of Concerns
A service should not bypass another domain's logic and reach directly into its storage unless there is a strong architectural reason.
For example, RecommendationService should ask HistoryService for recent wear information rather than directly querying history tables itself.
This keeps responsibility clear and reduces coupling.
The system becomes easier to test and maintain because each service can evolve behind a stable interface.
Service, Repository, and Database Layers
Business services should not usually contain database-specific logic.
A common backend flow is:
Controller → Service → Repository → Database
Each layer has a different responsibility:
- Controller: transport and request handling
- Service: business logic and business validity
- Repository: data access and persistence operations
- Database: storage and structural integrity
For example:
WardrobeService.getAvailableItems() may call WardrobeRepository.findAvailableItems(), which performs the actual database query.
This separation means the service can focus on what the application needs while the repository focuses on how the data is stored.
Validation Across Layers
Validation can happen at multiple layers because different layers protect different concerns.
The service layer protects business validity.
Examples:
- whether an action is allowed in the current context
- whether an outfit is appropriate for an important meeting
- whether a user has exceeded an application-level limit
The database protects data integrity.
Examples:
- required fields cannot be null
- scores cannot contain structurally invalid values
- foreign keys must reference valid records
Some rules belong in both layers.
For example, a negative warmth score can be rejected by the service for a clear user-facing error and also blocked by a database constraint as a final safety net.
A useful distinction is:
Database constraint = Can this data exist?
Service validation = Is this action valid in this situation?
Controller vs. Service
The controller owns the API boundary, not the recommendation logic.
For a request such as POST /recommendations, the controller can:
- receive the request
- validate required request fields and basic types
- call the RecommendationService
- translate the result into an HTTP response
The RecommendationService should handle the actual decision-making.
Similarly, if a request contains an outfitId, the controller can check that the field exists and has the correct shape, while the service determines whether that outfit exists for the user and whether the requested action is allowed.
This gives a useful summary:
- Controller = request validity
- Service = business validity
- Repository = persistence
- Database = integrity
Practical Use
Before implementing a feature, first write the decision flow in plain language.
For a recommendation system, ask:
- What are the inputs?
- What entities need to exist?
- Which data is intrinsic and which is historical?
- What should be filtered out completely?
- What remains eligible for scoring?
- Which criteria change with context?
- What feedback should be collected?
- Which behaviours should remain deterministic?
- Which behaviours could eventually be learned?
- Which module owns each responsibility?
- Which service owns each business rule?
- Which operations belong in repositories rather than services?
- Which constraints protect data integrity and which protect business validity?
This should make the eventual code, database schema, API boundaries, and tests easier to design.
A useful next exercise would be to convert this conceptual architecture into a small schema and a rule-based recommendation flow before introducing machine learning.
Related Experiments
Takeaways
- Start by decomposing the problem, not by writing code.
- Good data modelling makes business logic easier to express.
- Many-to-many relationships often need an explicit junction structure.
- Historical truth should not be silently changed when current data changes.
- Deterministic rules are useful when constraints are known; learning is useful where preferences are uncertain.
- User behaviour can provide stronger preference signals than explicit ratings alone.
- Personalization must account for context rather than assuming one global preference.
- Recommendation systems need a balance between exploiting known favourites and exploring new possibilities.
- Infer context when confidence is high and ask when ambiguity changes the decision.
- Filter hard constraints before ranking soft preferences.
- Use graceful degradation when no perfect solution exists.
- Separate modules by responsibility and services by business ownership.
- Keep persistence logic in repositories rather than business services.
- Treat controllers, services, repositories, and databases as distinct architectural concerns.
- Validate business rules in services and protect structural integrity in the database.