You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/faq.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,11 +4,12 @@
4
4
5
5
The outer layer of a tomato is strong enough to keep its insides together and yet flexible and fluid inside. That perfectly represents how our software should be designed.
6
6
7
-
**I am just kidding 😋 You like some made-up words, don't you?**
7
+
**I am just kidding 😋 You like some made-up fancy words, don't you?**
8
8
9
9
If you are okay with "Hexagonal," knowing 6 edges has no significance, you should be okay with "Tomato".
10
10
After all, we have Onion Architecture, why not Tomato Architecture 🤓
11
11
12
12
## I don't see anything new in this architecture. Why should I care?
13
13
14
-
There is nothing new in this architecture. It's just taking the good parts of other architectures and ignoring the cargo-cult programming. 😇
14
+
Exactly. There's nothing revolutionary here.
15
+
Tomato Architecture is just taking the good bits from existing architectures and leaving out the cargo-cult rituals. 😇
Copy file name to clipboardExpand all lines: docs/index.md
+48-60Lines changed: 48 additions & 60 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,10 +5,10 @@
5
5
**Tomato Architecture** is a pragmatic approach to software architecture following the **Core Principles**
6
6
7
7
## Core Principles
8
-
*Strive to keep things simple instead of overengineering the solution by guessing the requirements for the next decade.
9
-
*Do research, evaluate, then pick a technology and embrace it instead of creating abstractions with replaceability in mind.
10
-
*Make sure your solution is working as a whole, not just individual units.
11
-
*Think what is best for your software over blindly following suggestions by popular people.
8
+
*Focus on simplicity — solve today's problems without overengineering for hypothetical future needs.
9
+
*Research, evaluate, and then commit to the right technologies instead of building layers of unnecessary abstraction for the sake of "future replaceability."
10
+
*Design systems that work cohesively as a whole, not just as isolated, well-tested components.
11
+
*Make architectural decisions based on what truly benefits your software — not merely because it's a trend or endorsed by popular voices.
12
12
13
13
## Architecture Diagram
14
14
@@ -17,29 +17,27 @@
17
17
## Implementation Guidelines
18
18
19
19
### 1. Package by feature
20
-
A common pattern to organize code into packages is by splitting based on technical layers such as controllers, services, repositories, etc.
21
-
If you are building a Microservice which is already focusing on a specific module or business capability, then this approach might be fine.
20
+
A common way to organize code is by technical layers — separating controllers, services, repositories, and so on.
21
+
While this approach can work well for a microservice that already represents a single, well-defined business capability, it often becomes cumbersome in larger applications.
22
22
23
-
If you are building a monolith or modular-monolith, then it is strongly recommended to first split by features instead of technical layers.
23
+
For monolithic or modular-monolithic architectures, it is strongly recommended to organize code by feature rather than by technical layer. This structure keeps related functionality together, making the codebase easier to navigate, maintain, and evolve.
24
24
25
-
For more info read: [https://phauer.com/2020/package-by-feature/](https://phauer.com/2020/package-by-feature/)
25
+
For a deeper dive, check out: [https://phauer.com/2020/package-by-feature/](https://phauer.com/2020/package-by-feature/)
26
26
27
27
### 2. "Application Core" for Business Logic
28
-
Keep "Application Core" independent of any delivery mechanism (Web, Scheduler Jobs, CLI).
28
+
Keep the **Application Core** completely independent of any delivery mechanism — whether it's a web API, scheduled job, or command-line interface.
29
29
30
-
The Application Core should expose APIs that can be invoked from a main() method.
31
-
To achieve that, the "Application Core" should not depend on its invocation context.
32
-
Which means the "Application Core" should not depend on any HTTP/Web layer libraries.
33
-
Similarly, if your Application Core is being used from Scheduled Jobs or CLI, then
34
-
any Scheduling logic or CLI command execution logic should never leak into Application Core.
30
+
The core should expose well-defined APIs that can be invoked directly from a simple `main()` method.
31
+
To achieve this, the Application Core must remain agnostic to its execution context — it should not depend on HTTP frameworks, web libraries, or any delivery-specific components.
32
+
33
+
Likewise, if the same core logic is reused in scheduled jobs or CLI tools, ensure that scheduling or command execution details never leak into the Application Core.
35
34
36
35
### 3. Separation of Concerns
37
-
Separate the business logic execution from input sources (Web Controllers, Message Listeners, Scheduled Jobs, etc).
36
+
Keep the business logic execution independent of how the input arrives — whether through web controllers, message listeners, or scheduled jobs.
38
37
39
-
The input sources such as Web Controllers, Message Listeners, Scheduled Jobs, etc. should be a thin layer extracting the data
40
-
from request and delegate the actual business logic execution to "Application Core".
38
+
Input sources should act as thin layer to extract the required data from the request source and delegate all business logic to the Application Core.
41
39
42
-
**DON'T DO THIS**
40
+
**🚫 DON'T DO THIS**
43
41
44
42
```java
45
43
@RestController
@@ -57,7 +55,7 @@ class CustomerController {
57
55
}
58
56
```
59
57
60
-
**INSTEAD, DO THIS**
58
+
**✅ INSTEAD, DO THIS**
61
59
62
60
```java
63
61
@RestController
@@ -85,10 +83,9 @@ class CustomerService {
85
83
}
86
84
```
87
85
88
-
With this approach, whether you try to create a Customer from a REST API call or from a CLI,
89
-
all the business logic is centralized in Application Core.
86
+
With this approach, whether a customer is created via REST, CLI, or any other interface, the business logic remains centralized in the Application Core.
90
87
91
-
**DON'T DO THIS**
88
+
**🚫 DON'T DO THIS**
92
89
93
90
```java
94
91
@Component
@@ -110,7 +107,7 @@ class OrderProcessingJob {
110
107
}
111
108
```
112
109
113
-
**INSTEAD, DO THIS**
110
+
**✅ INSTEAD, DO THIS**
114
111
115
112
```java
116
113
@Component
@@ -135,16 +132,13 @@ class OrderService {
135
132
}
136
133
```
137
134
138
-
With this approach, you can decouple order processing logic from scheduler
139
-
and can test independently without triggering through Scheduler.
135
+
This decouples order processing logic from the scheduler, allowing you to test and reuse it independently of any scheduling mechanism.
140
136
141
-
From the Application Core we may talk to database, message brokers or 3rd party web services, etc.
142
-
Care must be taken such that business logic executors do not heavily depend on External Service Integrations.
137
+
Your Application Core may interact with databases, message brokers, or third-party services — but ensure these integrations don't leak into your business logic.
143
138
144
-
For example, assume you are using Spring Data JPA for persistence, and
145
-
from your **CustomerService** you would like to fetch customers using pagination.
139
+
For example, when using a persistence framework like Spring Data JPA, avoid tying your service logic directly to its APIs.
146
140
147
-
**DON'T DO THIS**
141
+
**🚫 DON'T DO THIS**
148
142
149
143
```java
150
144
@Service
@@ -160,7 +154,7 @@ class CustomerService {
160
154
}
161
155
```
162
156
163
-
**INSTEAD, DO THIS**
157
+
**✅ INSTEAD, DO THIS**
164
158
165
159
```java
166
160
@Service
@@ -183,12 +177,12 @@ class JpaCustomerRepository {
183
177
}
184
178
```
185
179
186
-
This way any persistence library changes will only affect the repository layer.
180
+
This way, changes to your persistence framework or infrastructure only affect the repository layer, leaving your business logic clean and stable.
187
181
188
182
### 4. Domain logic in domain objects
189
-
Keep domain logic in domain objects.
183
+
Keep domain behavior close to the data it operates on — inside your domain objects.
190
184
191
-
**DON'T DO THIS**
185
+
**🚫 DON'T DO THIS**
192
186
193
187
```java
194
188
@@ -212,10 +206,10 @@ class CartService {
212
206
}
213
207
```
214
208
215
-
Here `calculateCartTotal()` method contains domain logic purely based on the state of `Cart` object.
216
-
So, it should be part of the domain object `Cart`.
209
+
In this example, the `calculateCartTotal()` method contains pure domain logic based solely on the state of `Cart`.
210
+
Such logic belongs inside the `Cart`domain object itself — not in a service class.
217
211
218
-
**INSTEAD, DO THIS**
212
+
**✅ INSTEAD, DO THIS**
219
213
220
214
```java
221
215
@@ -240,27 +234,24 @@ class CartService {
240
234
```
241
235
242
236
### 5. No unnecessary interfaces
243
-
Don't create interfaces with the hope that someday we might add another implementation for this interface.
244
-
If that day ever comes, then with the powerful IDEs we have now, it is just a matter of extracting the interface in a couple of keystrokes.
237
+
Don't create interfaces just because "we might need another implementation someday."
238
+
If that day ever comes, modern IDEs make it trivial to extract an interface with just a few keystrokes.
245
239
246
-
If the reason for creating an interface is for testing with Mock implementation,
247
-
we have mocking libraries like Mockito which are capable of mocking classes without implementing interfaces.
240
+
If your only reason for introducing an interface is to make testing easier — you don't need to.
241
+
Mocking frameworks like Mockito can mock concrete classes just fine.
248
242
249
-
So, unless there is a good reason, don't create interfaces.
243
+
In short, only create interfaces when you have a clear and valid reason — not out of habit or hypothetical future needs.
250
244
251
245
### 6. Embrace framework's power and flexibility
252
-
Usually, the libraries and frameworks are created to address the common requirements that are required for a majority of the applications.
253
-
So, when you choose a library/framework to build your application faster, then you should embrace it.
246
+
Frameworks and libraries are built to solve the common challenges that most applications face.
247
+
When you adopt one to accelerate development, make full use of its strengths — embrace it rather than hide it.
254
248
255
-
Instead of leveraging the power and flexibility offered by the selected framework,
256
-
creating an indirection or abstraction on top of the selected framework with the hope
257
-
that someday you might switch the framework to a different one is usually a very bad idea.
249
+
Creating extra layers of abstraction on top of your chosen framework, just in case you might switch to another someday, is usually counterproductive. It adds complexity without real benefit and often prevents you from leveraging the framework's full potential.
258
250
259
-
For example, Spring Framework provides declarative support for handling database transactions, caching, method-level security etc.
260
-
Introducing our own similar annotations and re-implementing the same features support
261
-
by delegating the actual handling to the framework is unnecessary.
251
+
For example, the Spring Framework already provides powerful, declarative support for transactions, caching, and method-level security.
252
+
Re-creating these mechanisms through custom annotations that merely delegate to Spring's features offers no real advantage.
262
253
263
-
Instead, it's better to either directly use the framework's annotations or compose the annotation with additional semantics if needed.
254
+
Instead, use the framework directly — or if you need additional semantics, compose your own annotations on top of it, like this:
264
255
265
256
```java
266
257
@Target(ElementType.TYPE)
@@ -275,17 +266,14 @@ public @interface UseCase {
275
266
}
276
267
```
277
268
269
+
This approach keeps your code expressive while still taking full advantage of the framework's capabilities.
270
+
278
271
### 7. Test not only units, but whole features
279
272
280
-
We should definitely write unit tests to test the units (business logic), by mocking external dependencies if required.
281
-
But it is more important to verify whether the whole feature is working properly or not.
273
+
Unit tests are essential — they validate individual pieces of business logic, often by mocking external dependencies. But beyond that, it's even more important to ensure that a complete feature actually works as expected.
282
274
283
-
Even if our unit tests are running in milliseconds, can we go to production with confidence? Of course not.
284
-
We should verify the whole feature is working or not by testing with the actual external dependencies such as database or message brokers.
285
-
That gives us more confidence.
275
+
Fast unit tests alone don't guarantee production readiness. To gain real confidence, test your features end-to-end, using real dependencies like databases, message brokers, or external services.
286
276
287
-
I wonder if this whole idea of "We should have core domain completely independent of external dependencies" philosophy
288
-
came from the time when testing with real dependencies is very challenging or not possible at all.
277
+
The popular idea that "the core domain must be completely independent of external dependencies" likely originated when testing with real infrastructure was difficult or impractical. Fortunately, that's no longer the case — tools like [Testcontainers](https://testcontainers.com/) make it easy to spin up real dependencies during tests.
289
278
290
-
Luckily, we have better technology now (ex: [Testcontainers](https://testcontainers.com/)) for testing with real dependencies.
291
-
Testing with real dependencies might take slightly more time, but compared to the benefits, that's a negligible cost.
279
+
Running such tests might take a bit longer, but the confidence and reliability they provide are well worth the trade-off.
0 commit comments