• Stars
    star
    431
  • Rank 97,637 (Top 2 %)
  • Language
  • License
    MIT License
  • Created about 1 year ago
  • Updated 4 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
611
star
2

generator-springboot

Yeoman based Spring Boot Microservice generator
Java
212
star
3

Java-Persistence-with-MyBatis3

MyBatis
Java
148
star
4

sivalabs-blog-samples-code

Code samples for my blog posts on https://sivalabs.in
JavaScript
142
star
5

maven-archetype-templates

Maven Archetype Templates
CSS
136
star
6

springboot-learn-by-example

SpringBoot Learn By Example Book
SCSS
75
star
7

spring-boot-tutorials

SpringBoot Tutorials
JavaScript
63
star
8

jcart

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

testcontainers-samples

Sample projects using Testcontainers for Java
HTML
54
star
10

techbuzz

A place for techie to share knowledge, blog posts, tech news etc
Java
37
star
11

spring-boot-application-template

Spring Boot Application Template
Java
31
star
12

intellij-live-templates

Intellij IDEA Live Templates
28
star
13

spring-boot-microservices-course

Spring Boot Microservices Course
Java
28
star
14

springboot-kubernetes-youtube-series

Code for "SpringBoot + Kubernetes Tutorial" YouTube Series
Java
26
star
15

devzone

A sample SpringBoot application
Java
24
star
16

java-testing-made-easy

Java Testing Made Easy YouTube Tutorial Series
Java
21
star
17

primefaces-beginners-guide

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

twitter4j-spring-boot-starter

SpringBoot Starter for Twitter4J
Java
12
star
19

spring-boot-opentelemetry-demo

Spring Boot Open Telemetry Demo
Java
12
star
20

spring-boot-tutorials-blog-series

Code for Spring Boot Tutorials Blog Series
Java
11
star
21

sivalabs-dev-notes

My tech notes for quick reference
10
star
22

progen

Project Generator CLI
Go
10
star
23

go-for-spring-boot-developers

Go for Spring Boot Developers
Go
10
star
24

tc-guides-bot

OpenAI demo using LangChain4j and SpringBoot
Java
9
star
25

spring-initializr-extensions

Spring Boot Starter Extensions
Java
9
star
26

spring-boot-3-observability-grafana-stack

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

modern-spring-boot

Modern Spring Boot features demo
Java
8
star
28

spring-boot-jooq-demo

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

beginning-spring-boot-2

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

spring-security-oauth2-microservices-demo

Spring Security OAuth2 Microservices Demo
Java
6
star
31

spring-boot-jpa-crud-demo

SpringBoot CRUD Demo Using JPA
Java
6
star
32

sivalabs-youtube-code-samples

Sample code for my YouTube video tutorials
Java
6
star
33

must-know-java-libraries

Must Know Java Libraries
Java
6
star
34

spring-boot-todolist

Demo spring-boot application to try out new features.
Java
5
star
35

jblogger

A blogging application using SpringBoot.
CSS
5
star
36

moviebuffs

A movie rental store application
Java
4
star
37

clone-and-run-devexp-using-testcontainers

Clone and Run Developer experience using Testcontainers
Java
4
star
38

config-repo

4
star
39

spring-boot-thymeleaf-alpinejs-demo

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

kafka-tutorial

Java
4
star
41

jenkins-shared-library

jenkins-shared-library
Groovy
3
star
42

cloud-native-microservices

Java
3
star
43

spring-boot-testcontainers-devmode

Spring Boot 3.1.0 Testcontainers support demo
Java
3
star
44

spring-boot-kubernetes-demo

Java
3
star
45

spring-boot-aws-kitchensink

Java
3
star
46

todo-list

Todo List Application using SpringBoot
Java
3
star
47

geeknight-cloud-native-apps-springcloud

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

java-ai-demos

Java AI Demos
Java
3
star
49

spring-boot-rest-api-antipatterns

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

spring-boot-redis-cache-demo

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

microservices-config-repo

2
star
52

jpa-playground

Java
2
star
53

kubernetes-workshop

Kubernetes Workshop samples
Java
2
star
54

spring-boot-kafka-demo

Java
2
star
55

spring-ai-samples

Spring AI Samples
Java
2
star
56

bangalore-jug-modern-spring-boot

Java
2
star
57

video-library-monolith

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

vote-service

Vote Service
Java
1
star
59

jte-the-basics-app-gradle

1
star
60

techbuzz-spring-boot-angular

SpringBoot + Angular Application
Java
1
star
61

goquik

CLI for generating Go applications
Go
1
star
62

Learning-FP

Java
1
star
63

spring-boot-cli

Go
1
star
64

bookmarks-api-springboot

Bookmarks APi SpringBoot
Java
1
star
65

sivaprasadreddy

GitHub README
1
star
66

testcontainers-spring-boot-demo

Testcontainers Spring Boot demo application
Java
1
star