• Stars
    star
    495
  • Rank 88,974 (Top 2 %)
  • Language
  • License
    MIT License
  • Created over 1 year ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Tomato Architecture - A common sense driven approach to software architecture

tomato-architecture-logo.png

Tomato Architecture is an approach to software architecture following Common Sense Manifesto

Common Sense Manifesto

  • Think what is best for your software over blindly following suggestions by popular people.
  • Strive to keep things simple instead of over-engineering the solution by guessing the requirements for the next decade.
  • Do R&D, pick a technology and embrace it instead of creating abstractions with replaceability in mind.
  • Make sure your solution is working as a whole, not just individual units.

Architecture Diagram

tomato-architecture.png

Implementation Guidelines

1. Package by feature

A common pattern to organize code into packages is by splitting based on technical layers such as controllers, services, repositories, etc. If you are building a Microservice which is already focusing on a specific module or business capability, then this approach might be fine.

If you are building a monolith or modular-monolith then it is strongly recommended to first split by features instead of technical layers.

For more info read: https://phauer.com/2020/package-by-feature/

2. Keep "Application Core" independent of delivery mechanism (Web, Scheduler Jobs, CLI)

The Application Core should expose APIs that can be invoked from a main() method. In order to achieve that, the "Application Core" should not depend on its invocation context. Which means the "Application Core" should not depend on any HTTP/Web layer libraries. Similarly, if your Application Core is being used from Scheduled Jobs or CLI then any Scheduling logic or CLI command execution logic should never leak into Application Core.

3. Separate the business logic execution from input sources (Web Controllers, Message Listeners, Scheduled Jobs etc)

The input sources such as Web Controllers, Message Listeners, Scheduled Jobs, etc should be a thin layer extracting the data from request and delegate the actual business logic execution to "Application Core".

DON'T DO THIS

@RestController
class CustomerController {
    private final CustomerService customerService;
    
    @PostMapping("/api/customers")
    void createCustomer(@RequestBody Customer customer) {
       if(customerService.existsByEmail(customer.getEmail())) {
           throw new EmailAlreadyInUseException(customer.getEmail());
       }
       customer.setCreateAt(Instant.now());
       customerService.save(customer);
    }
}

INSTEAD, DO THIS

@RestController
class CustomerController {
    private final CustomerService customerService;
    
    @PostMapping("/api/customers")
    void createCustomer(@RequestBody Customer customer) {
       customerService.save(customer);
    }
}

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   void save(Customer customer) {
      if(customerRepository.existsByEmail(customer.getEmail())) {
         throw new EmailAlreadyInUseException(customer.getEmail());
      }
      customer.setCreateAt(Instant.now());
      customerRepository.save(customer);
   }
}

With this approach, whether you try to create a Customer from a REST API call or from a CLI, all the business logic is centralized in Application Core.

DON'T DO THIS

@Component
class OrderProcessingJob {
    private final OrderService orderService;
    
    @Scheduled(cron="0 * * * * *")
    void run() {
       List<Order> orders = orderService.findPendingOrders();
       for(Order order : orders) {
           this.processOrder(order);
       }
    }
    
    private void processOrder(Order order) {
       ...
       ...
    }
}

INSTEAD, DO THIS

@Component
class OrderProcessingJob {
   private final OrderService orderService;

   @Scheduled(cron="0 * * * * *")
   void run() {
      List<Order> orders = orderService.findPendingOrders();
      orderService.processOrders(orders);
   }
}

@Service
@Transactional
class OrderService {

   public void processOrders(List<Order> orders) {
       ...
       ...
   }
}

With this approach, you can decouple order processing logic from scheduler and can test independently without triggering through Scheduler.

4. Don't let the "External Service Integrations" influence the "Application Core" too much

From the Application Core we may talk to database, message brokers or 3rd party web services, etc. Care must be taken such that business logic executors not heavily depend on External Service Integrations.

For example, assume you are using Spring Data JPA for persistence, and from your CustomerService you would like fetch customers using pagination.

DON'T DO THIS

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   PagedResult<Customer> getCustomers(Integer pageNo) {
      Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
      Page<Customer> cusomersPage = customerRepository.findAll(pageable);
      return convertToPagedResult(cusomersPage);
   }
}

INSTEAD, DO THIS

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   PagedResult<Customer> getCustomers(Integer pageNo) {
      return customerRepository.findAll(pageNo);
   }
}

@Repository
class JpaCustomerRepository {

   PagedResult<Customer> findAll(Integer pageNo) {
      Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
      return ...;
   }
}

This way any persistence library changes will only affect repository layer only.

5. Keep domain logic in domain objects

If you have domain object state change methods that affect only that object or a method to calculate something from the state of the object, then those methods belong to that domain object.

DON'T DO THIS

class Cart {
    List<LineItem> items;
}

@Service
@Transactional
class CartService {

   CartDTO getCart(UUID cartId) {
      Cart cart = cartRepository.getCart(cartId);
      BigDecimal cartTotal = this.calculateCartTotal(cart);
      ...
   }
   
   private BigDecimal calculateCartTotal(Cart cart) {
      ...
   }
}

INSTEAD, DO THIS

class Cart {
    List<LineItem> items;

   public BigDecimal getTotal() {
      ...
   }
}

@Service
@Transactional
class CartService {

   CartDTO getCart(UUID cartId) {
      Cart cart = cartRepository.getCart(cartId);
      BigDecimal cartTotal = cart.getTotal();
      ...
   }
}

6. No unnecessary interfaces

Don't create interfaces with the hope that someday we might add another implementation for this interface. 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.

If the reason for creating an interface is for testing with Mock implementation, we have mocking libraries like Mockito which is capable of mocking classes without implementing interfaces.

So, unless there is a good reason, don't create interfaces.

7. Embrace the framework's power and flexibility

Usually, the libraries and frameworks are created to address the common requirements that are required for majority of the applications. So, when you choose a library/framework to build your application faster, then you should embrace it.

Instead of leveraging the power and flexibility offered by the selected framework, creating an indirection or abstraction on top of the selected framework with the hope that someday you might switch the framework to a different one is usually a very bad idea.

For example, Spring Framework provides declarative support for handling database transactions, caching, method-level security etc. Introducing our own similar annotations and re-implementing the same features support by delegating the actual handling to the framework is unnecessary.

Instead, it's better to either directly use the framework's annotations or compose the annotation with additional semantics if needed.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Transactional
public @interface UseCase {
   @AliasFor(
        annotation = Transactional.class
   )
   Propagation propagation() default Propagation.REQUIRED;
}

8. Test not only units, but whole features

We should definitely write unit tests to test the units(business logic), by mocking external dependencies if required. But it is more important to verify whether the whole feature is working properly or not.

Even if our unit tests are running in milliseconds, can we go to production with confidence? Of course not. We should verify the whole feature is working or not by testing with the actual external dependencies such as database or message brokers. That gives us more confidence.

I wonder this whole idea of "We should have core domain completely independent of external dependencies" philosophy came from the time when testing with real dependencies is very challenging or not possible at all.

Luckily, we have better technology now (ex: Testcontainers) to test with real dependencies. Testing with real dependencies might take slightly more time, but compared to the benefits, that's a negligible cost.

FAQs

  1. What's with the name "Tomato"?

    If you are okay with "Hexagonal" knowing 6 edges has no significance, you should be okay with "Tomato". After all, we have Onion Architecture, why not Tomato:-)

  2. What if they call me "code monkey" for following this architecture?

    Ignore them. Focus on delivering the business value.

More Repositories

1

spring-boot-microservices-series

Code for SpringBoot MicroServices Blog Series
Java
627
star
2

generator-springboot

Yeoman based Spring Boot Microservice generator
Java
217
star
3

software-architecture-premier

Software Architecture Premier Presentation
156
star
4

Java-Persistence-with-MyBatis3

MyBatis
Java
148
star
5

sivalabs-blog-samples-code

Code samples for my blog posts on https://sivalabs.in
JavaScript
143
star
6

maven-archetype-templates

Maven Archetype Templates
CSS
137
star
7

springboot-learn-by-example

SpringBoot Learn By Example Book
SCSS
75
star
8

spring-boot-tutorials

SpringBoot Tutorials
JavaScript
64
star
9

spring-boot-microservices-course

YouTube Spring Boot Microservices Course Application
Java
61
star
10

jcart

JCart is a simple e-commerce application built with Spring.
Java
60
star
11

testcontainers-samples

Sample projects using Testcontainers for Java
Java
55
star
12

tomato-architecture-spring-boot-demo

A sample Spring Boot application following Tomato Architecture
Java
46
star
13

techbuzz

A place for techie to share knowledge, blog posts, tech news etc
Java
44
star
14

spring-modular-monolith

An application following Spring Modulith
Java
35
star
15

intellij-live-templates

Intellij IDEA Live Templates
33
star
16

spring-boot-application-template

Spring Boot Application Template
Java
32
star
17

springboot-kubernetes-youtube-series

Code for "SpringBoot + Kubernetes Tutorial" YouTube Series
Java
30
star
18

java-testing-made-easy

Java Testing Made Easy YouTube Tutorial Series
Java
26
star
19

devzone

A sample SpringBoot application
Java
25
star
20

primefaces-beginners-guide

PrimeFaces Beginner's Guide Book Source Code
Java
15
star
21

spring-boot-tutorials-blog-series

Code for Spring Boot Tutorials Blog Series
Java
14
star
22

progen

Project Generator CLI
Go
14
star
23

twitter4j-spring-boot-starter

SpringBoot Starter for Twitter4J
Java
12
star
24

spring-boot-opentelemetry-demo

Spring Boot Open Telemetry Demo
Java
12
star
25

sivalabs-youtube-code-samples

Sample code for my YouTube video tutorials
Java
11
star
26

spring-realworld-conduit-api

Spring RealWorld Conduit API
Java
11
star
27

sivalabs-dev-notes

My tech notes for quick reference
10
star
28

modern-spring-boot

Modern Spring Boot features demo
Java
10
star
29

go-for-spring-boot-developers

Go for Spring Boot Developers
Go
10
star
30

tc-guides-bot

OpenAI demo using LangChain4j and SpringBoot
Java
9
star
31

spring-initializr-extensions

Spring Boot Starter Extensions
Java
9
star
32

spring-boot-3-observability-grafana-stack

SpringBoot 3 Observability using Grafana Stack(Grafana, Prometheus, Loki, Tempo)
Java
8
star
33

spring-boot-todolist

Demo spring-boot application to try out new features.
Java
7
star
34

spring-boot-jooq-demo

SpringBoot + JOOQ Demo application demonstrating code generation using Testcontainers with Maven/Gradle and how to load associations
Java
7
star
35

beginning-spring-boot-2

Source code for my Beginning Spring Boot 2 book
Java
6
star
36

spring-security-oauth2-microservices-demo

Spring Security OAuth2 Microservices Demo
Java
6
star
37

spring-boot-jpa-crud-demo

SpringBoot CRUD Demo Using JPA
Java
6
star
38

jblogger

A blogging application using SpringBoot.
CSS
6
star
39

must-know-java-libraries

Must Know Java Libraries
Java
6
star
40

spring-ai-samples

Spring AI Samples
Java
6
star
41

intellij-idea-livestream-26-sept-2024

IntelliJ IDEA Livestream 26-sept-2024 code
Java
5
star
42

spring-style-docs

Spring style documentation using Asciidoctor
Java
5
star
43

moviebuffs

A movie rental store application
Java
4
star
44

clone-and-run-devexp-using-testcontainers

Clone and Run Developer experience using Testcontainers
Java
4
star
45

config-repo

4
star
46

spring-boot-thymeleaf-alpinejs-demo

A sample application using Spring Boot, Thymeleaf and Alpine.js
HTML
4
star
47

kafka-tutorial

Java
4
star
48

sivalabs-learning-paths

SivaLabs Learning Paths
4
star
49

streamlit-ollama-chatbot

Ollama Chatbot using Streamlit
Python
3
star
50

jenkins-shared-library

jenkins-shared-library
Groovy
3
star
51

cloud-native-microservices

Java
3
star
52

spring-boot-testcontainers-devmode

Spring Boot 3.1.0 Testcontainers support demo
Java
3
star
53

spring-boot-kubernetes-demo

Java
3
star
54

spring-boot-aws-kitchensink

Java
3
star
55

geeknight-cloud-native-apps-springcloud

Code for Cloud Native Applications using Spring Boot, Spring Cloud talk
Shell
3
star
56

langchain4j-demos

LangChain4j demos
Java
3
star
57

spring-boot-rest-api-antipatterns

A sample repo demonstrating common anti-patteerns in Spring Boot REST APIs
Java
3
star
58

bootiful-blog

A blog web application and REST API implemented using Spring Boot
Java
2
star
59

sivalabs-talks

2
star
60

spring-boot-redis-cache-demo

https://github.com/spring-projects/spring-boot/issues/27577
Java
2
star
61

microservices-config-repo

2
star
62

jpa-playground

Java
2
star
63

kubernetes-workshop

Kubernetes Workshop samples
Java
2
star
64

spring-boot-kafka-demo

Java
2
star
65

bangalore-jug-modern-spring-boot

Java
2
star
66

spring-ai-geek-talk

Spring AI Geek Talk
Java
1
star
67

bookmarks

Spring Boot CRUD REST API Demo Application
Java
1
star
68

video-library-monolith

Video Library application following monolithic architecture using SpringBoot
Java
1
star
69

techbuzz-spring-boot-angular

SpringBoot + Angular Application
Java
1
star
70

vote-service

Vote Service
Java
1
star
71

jte-the-basics-app-gradle

1
star
72

goquik

CLI for generating Go applications
Go
1
star
73

Learning-FP

Java
1
star
74

spring-boot-cli

Go
1
star
75

bookmarks-api-springboot

Bookmarks APi SpringBoot
Java
1
star
76

sivaprasadreddy

GitHub README
1
star
77

sample-spring-boot-app

Sample Spring Boot App
Java
1
star
78

testcontainers-spring-boot-demo

Testcontainers Spring Boot demo application
Java
1
star