LearningTree

Additional Design
Considerations

Programming paradigms, generative approaches, annotations, AOP, and cross-cutting concerns in software architecture.

01
Chapter One

Programming Language Features & Paradigms to Reduce Complexity

Design patterns and principles can be realized through real tools, technologies, and programming language features. Generative approaches reduce boilerplate, while language paradigms enforce correctness at the type-system level β€” both directly reduce complexity in software architecture.

Generative Creation of Components

Instead of writing repetitive code by hand, generative approaches let you describe what you want at a higher level β€” and the tooling produces the actual code for you.

  • β†’  Use macros, templates, schemas, or transpilers as your source
  • β†’  Eliminate manual boilerplate β€” less code to write and maintain
  • β†’  Get consistent output across multiple languages and platforms
  • β†’  Keep a single source of truth that enforces DRY automatically
βš™οΈ

Macros

  • C/C++ Preprocessor β€” named fragments replaced at compile time
  • Enforce DRY by expanding repetitive code patterns automatically
  • Result is high-performance runtime code
πŸ“„

Template-Based Generators

  • JET β€” Java Emitter Templates
  • JSP / JSF β€” Jakarta Server Pages / Faces
  • Thymeleaf, FreeMarker β€” server-side Java templating
  • Follows DRY + SoC; output is static & high performance
πŸ”„

Transpilers

  • TypeScript β†’ JavaScript (adds type safety)
  • Wt β€” C++ Web Toolkit
  • JWt β€” Java Web Toolkit
  • Cross-platform: Xamarin, React Native, Flutter, Phone Gap
Macros β€” C/C++ Preprocessor Example

C/C++ macros let you define a reusable fragment once and expand it everywhere at compile time. The preprocessor replaces every occurrence before the compiler even sees the code β€” zero runtime overhead, pure DRY.

❌ Without Macros β€” Repeated Code

Same bounds-check duplicated everywhere
void setTemperature(int val) { if (val < MIN || val > MAX) { printf("Error: out of range\n"); return; } temperature = val; } void setPressure(int val) { if (val < MIN || val > MAX) { printf("Error: out of range\n"); return; } pressure = val; }

βœ… With Macros β€” Define Once, Reuse

Macro expands the check at compile time
// Define the macro once #define CHECK_RANGE(val, min, max) \ if ((val) < (min) || (val) > (max)) { \ printf("Error: %d out of range\n", val); \ return; \ } // Reuse everywhere β€” DRY void setTemperature(int val) { CHECK_RANGE(val, -40, 125); temperature = val; } void setPressure(int val) { CHECK_RANGE(val, 0, 1000); pressure = val; }
Trade-off: Macros are powerful but have no type safety β€” the preprocessor does plain text substitution. Use them for small, well-defined patterns; prefer inline functions or templates in modern C++ for complex logic.
Template-Based Generators β€” Thymeleaf Example

Template engines mix static HTML with dynamic placeholders. At build or request time the engine fills in the data and outputs pure HTML β€” the template is the single source of truth for layout, while the data comes from the backend.

Thymeleaf Template (source)

HTML + Thymeleaf expressions
<!-- product-list.html --> <table> <tr th:each="p : ${products}"> <td th:text="${p.name}">Name</td> <td th:text="${p.price}">0.00</td> <td> <span th:if="${p.inStock}" th:text="'In Stock'"></span> <span th:unless="${p.inStock}" th:text="'Sold Out'"></span> </td> </tr> </table>

Generated HTML (output)

Pure HTML β€” served to the browser
<!-- rendered at request time --> <table> <tr> <td>Coffee Mug</td> <td>19.99</td> <td><span>In Stock</span></td> </tr> <tr> <td>Laptop Stand</td> <td>49.99</td> <td><span>Sold Out</span></td> </tr> </table>
SoC in action: The designer owns the HTML template; the developer owns the data model. Neither touches the other's work β€” changes to layout don't break logic and vice versa.
Transpilers β€” TypeScript β†’ JavaScript

TypeScript adds a compile-time type system on top of JavaScript. The transpiler strips types and outputs standard JS β€” catching entire classes of bugs before runtime at zero cost to performance.

TypeScript β€” Source (typed)

class Product { constructor( public name: string, public price: number ) {} displayDetails() { console.log( `Product: ${this.name}, Price: ${this.price}` ); } } const product = new Product("Coffee Mug", 19); product.displayDetails();

JavaScript β€” Generated (runtime-ready)

"use strict"; var Product = (function() { function Product(name, price) { this.name = name; this.price = price; } Product.prototype.display = function() { console.log(this.name + " costs $" + this.price); }; return Product; })(); var coffeeMug = new Product("Coffee Mug", 20); coffeeMug.display();
API-Based Generators β€” gRPC + Protocol Buffers

gRPC uses Protocol Buffers to define a .proto schema once, then generates type-safe client/server stubs for Java, Python, C#, Go, and more β€” enforcing contracts across service boundaries.

πŸ“„ product.proto β€” The Schema (source of truth)

Protocol Buffers schema β€” defined once
// product.proto syntax = "proto3"; package product; // ── Messages ────────────────────── message Product { string id = 1; string name = 2; double price = 3; bool inStock = 4; } message ProductRequest { string id = 1; } message ProductListResponse { repeated Product products = 1; } // ── Service ─────────────────────── service ProductService { rpc GetProduct (ProductRequest) returns (Product); rpc ListProducts (ProductRequest) returns (ProductListResponse); }

βš™οΈ How it works

  • Define messages (data structures) and services (RPC methods) in one .proto file
  • Run protoc β€” the Protocol Buffer compiler β€” targeting any language
  • The compiler outputs type-safe stubs: data classes + client/server interfaces
  • All languages share the same contract β€” no drift, no manual mapping
DRY at the API level: One schema β†’ Java, Python, C#, Go stubs. Change the .proto and regenerate β€” every language stays in sync automatically.
β˜•

Generate Java Code

protoc --java_out=. \ --grpc-java_out=. \ -I. product.proto
🐍

Generate Python Code

python -m grpc_tools.protoc \ --python_out=. \ --grpc_python_out=. \ -I. product.proto
πŸ”·

Generate C# Code

protoc --csharp_out=. \ --grpc-csharp_out=. \ -I. product.proto
Key Insight: Define the schema once β†’ generate for every language. This is KISS + DRY applied at the inter-service contract level.
Programming Language Paradigms

Programming paradigms provide structural building blocks that manage complexity at different levels β€” from naming and grouping code, to enforcing type safety and memory ownership at compile time.

πŸ“¦

Functions & Procedures

  • SRP, Modularity, SoC, DRY β€” core principles realized
  • Decompose behaviour into named, reusable units
  • Enable isolated testing and reasoning
🧬

OOP β€” Objects & Inheritance

  • Encapsulation, polymorphism, abstraction
  • Class hierarchies and interfaces
  • Modelling real-world domains in code
πŸ“‚

Modules & Packages

  • Logical grouping of related code
  • Namespace isolation prevents naming conflicts
  • Enables independent compilation & deployment
πŸ”’

Access Modifiers

  • public β€” accessible anywhere
  • private β€” class-only
  • protected β€” subclasses
  • internal β€” same assembly / package
Advanced Language Features

Modern languages go beyond basic paradigms with features that catch entire categories of errors at compile time β€” type systems, ownership models, async primitives, and optional types.

🏷️

Type Systems

  • Catch type errors at compile time before they reach production
  • C++: int, double, bool, string
  • Prevents misuse: string phone = 053… β†’ compile error
  • Stronger types = fewer runtime surprises
🧲

Ownership Models

  • Memory safety: each object has a clear owner
  • Prevents memory leaks, dangling pointers, data races
  • Helps with concurrency: no shared mutable state
  • unique_ptr (single owner) vs shared_ptr (reference counted)
⏳

Promises & Futures

  • Async operations without blocking the main thread
  • JS: Promise.then(success, failure)
  • Chain multiple async results declaratively
  • Eliminates "callback hell" and race conditions
❓

Optionals

  • "Expect Errors" principle β€” Postel's Law in code
  • Java: Optional<User>
  • Check presence before access β€” avoids NPEs
  • Makes absence of a value explicit in the type system
πŸ“‹ Chapter 1 β€” Summary
  • Generative creation β€” Macros, templates, transpilers, and API generators (gRPC) produce code from higher-level descriptions, enforcing DRY and reducing boilerplate
  • Transpilers β€” TypeScript β†’ JS adds type safety at zero runtime cost; cross-platform frameworks (Flutter, React Native) target multiple devices from one codebase
  • API-based generators β€” Define a .proto schema once β†’ generate type-safe stubs for every language
  • Paradigms β€” Functions (SRP), OOP (encapsulation), Modules (namespace isolation), and Access Modifiers (information hiding) directly reduce complexity
  • Advanced features β€” Type systems, ownership models, promises/futures, and optionals catch entire categories of errors at compile time
02
Chapter Two

Annotations and Aspect-Oriented Programming

Annotations (decorators) add metadata to code elements, evaluated at compile or runtime. AOP separates cross-cutting concerns from business logic, weaving them in transparently β€” applying DRY, SoC, and OCP to concerns like logging, security, and performance monitoring.

Annotations (Decorators) β€” Concept
🏷️

What are Annotations?

  • Decorate functions, classes, methods, or properties with metadata
  • Evaluated by an annotation processor at compile time or runtime
  • Provide an abstraction layer to manage complexity
  • Enable cleaner, more concise, and more maintainable code
  • Help build more reliable and scalable systems

Compile-Time β€” Lombok Example

Java + Lombok β€” Code Generation via Annotations
@Getter @Setter @AllArgsConstructor @NoArgsConstructor @ToString public class Product { private String id; private String name; private double price; } // β†’ Lombok generates all getters, setters, // constructors, and toString() at compile time
Annotations β€” Runtime Examples

Runtime annotations are evaluated by frameworks at startup or during execution. They wire dependencies, map objects to database tables, expose REST endpoints, and more β€” all without manual boilerplate.

Spring DI β€” @Bean Configuration

Runtime β€” Spring Container wires beans at startup
@Configuration public class AppConfig { @Bean public NotificationService notificationService() { return new NotificationService(); } @Bean public ProductService productService(NotificationService ns) { return new ProductService(ns); // injected } }

Hibernate ORM β€” @Entity Mapping

Runtime β€” Hibernate maps objects to database tables
@Entity @Table(name = "employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "first_name") private String firstName; @Column(name = "email") private String email; }
Aspect-Oriented Programming (AOP)

AOP increases modularity by separating cross-cutting concerns (logging, security, transaction management) from business logic. The concern logic is defined once in an Aspect and "woven" into business code at compile or runtime.

🎯

What is AOP?

  • Increases modularity by separating cross-cutting concerns
  • Weaves concern logic into business logic at compile or runtime
  • Applies DRY + SoC + OCP to concerns like logging & security
πŸ”‘

Key Concepts

  • Aspect β€” component encapsulating cross-cutting logic
  • Advice β€” the action to perform (before, after, around)
  • Join Point β€” point in execution where advice is applied
  • Pointcut β€” expression to select matching join points
βœ…

Benefits

  • Business logic free of cross-cutting noise
  • Single place to change concern behaviour
  • Cleaner, more readable service classes
  • More reliable and maintainable systems
AOP β€” Weaving Aspects into Business Logic
BUSINESS SERVICES ProductService OrderService UserService WEAVER ASPECTS (CROSS-CUTTING) Logging Security Transactions Caching Monitoring Error Handling Each aspect is defined once β†’ woven into all matching services
AOP in Practice β€” Before & After

❌ Before AOP β€” Logging Mixed In

Logging duplicated in every service method
@Service public class ProductService { public void updateInventory(Long id, int stock) { // manually log every service call... logger.info("updateInventory called"); long start = System.currentTimeMillis(); // business logic here logger.info("Took " + (System.currentTimeMillis() - start)); } } @Service public class OrderService { public void processOrder(Long orderId) { // same logging boilerplate repeated... logger.info("processOrder called"); long start = System.currentTimeMillis(); // business logic here logger.info("Took " + (System.currentTimeMillis() - start)); } }

βœ… After AOP β€” Aspect Handles Logging

Logging defined once, woven by Spring AOP
@Aspect @Component public class ExecutionTimeAspect { @Around("@annotation(LogExecutionTime)") public Object logTime( ProceedingJoinPoint joinPoint ) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long elapsed = System.currentTimeMillis() - start; System.out.println( joinPoint.getSignature() + " executed in " + elapsed + "ms"); return result; } }
Result: Services simply annotate methods with @LogExecutionTime β€” no logging logic in business code. The aspect is "woven in" by the Spring AOP framework at runtime, keeping business logic clean and cross-cutting concerns centralized.
πŸ“‹ Chapter 2 β€” Summary
  • Annotations β€” Metadata decorators evaluated at compile time (Lombok: @Getter, @Setter) or runtime (Spring: @Bean, @Configuration; Hibernate: @Entity, @Column)
  • Compile-time annotations generate code automatically (getters, setters, constructors) β€” eliminating boilerplate
  • Runtime annotations wire dependencies, map ORM entities, expose REST endpoints β€” all without manual configuration
  • AOP β€” Separates cross-cutting concerns (logging, security, transactions) from business logic via Aspects
  • AOP Key Concepts: Aspect (the concern module), Advice (the action), Join Point (where it applies), Pointcut (the selector expression)
  • Benefits: DRY + SoC + OCP β€” define each concern once, change it in one place, business code stays clean
03
Chapter Three

Cross-Cutting Concerns and Concepts

Every system has concerns that don't belong to a single component β€” they span the entire architecture. These are called cross-cutting concerns.

πŸ“–

What are they?

  • Aspects of the system that affect multiple components simultaneously
  • Cannot be cleanly isolated into a single independent module
  • Arise naturally from quality requirements (security, performance, reliability)
⚠️

The Problem

  • If hard-coded everywhere β†’ violates DRY & SoC
  • Logic gets scattered across many components
  • A single change (e.g. log format) requires touching every class
  • Testing becomes difficult β€” concerns are tangled with business logic
βœ…

The Solution

  • Identify and centralise each concern in one place
  • Use AOP, frameworks, or dedicated infrastructure modules
  • Apply consistently β€” every component benefits automatically
  • Change once β†’ propagates everywhere
Cross-Cutting Concerns β€” Cutting Through All Components
Presentation UI / API layer Business domain logic Data Access repos / ORM Integration APIs / events Infrastructure config / cloud πŸ” SECURITY πŸ“‹ LOGGING ⚠️ ERROR HANDLING πŸ“Š MONITORING Each concern cuts horizontally across every component in the system
Common Cross-Cutting Concerns β€” Overview
Persistence
Error Handling
Authentication
Authorization
I18N
Communication
GUI / UX
Business Rules
Logging
Caching
Concurrency

"Cross-cutting concerns cannot be easily separated into independent components β€” they may violate DRY/SoC and can lead to code scattered across different parts of the system."

Persistence

Persist data from volatile memory to permanent storage. The goal is to decouple business domain from database infrastructure and provide a single persistence solution for the entire application.

πŸ’Ύ

What & Why

  • Persist data from volatile memory to permanent storage
  • Decouple business domain from database infrastructure
  • Abstract database interactions into high-level object operations
  • Provide a single persistence solution for the entire application
πŸ—„οΈ

ORM Framework Examples

  • Hibernate β€” Java
  • Entity Framework β€” .NET
  • Sequelize β€” Node.js
  • SQLAlchemy β€” Python
relational key/value file-based object-oriented graph-oriented document
Error Handling

A uniform, system-wide strategy for handling errors ensures predictable responses depending on category and severity, minimizes the blast radius of failures, and facilitates diagnosis.

⚠️

Goals of Error Handling

  • Predictable responses depending on category & severity
  • Differentiate between logical and technical errors
  • Minimize effects of errors on the system
  • Uniform & consistent across different technologies
  • Facilitate diagnosis β€” what / where / why
  • Error prevention β€” timely warning of identifiable problems

HTTP Status Codes

CodeMeaning
400Bad Request
401Unauthorized
404Not Found
405Method Not Allowed
500Internal Server Error
503Service Unavailable
507Insufficient Storage
Security

Security is a first-class architectural concern β€” it should be designed in from the start, not retrofitted. It spans authentication, authorization, data integrity, confidentiality, and system availability.

πŸ”‘

Authentication & Authorization

  • Authentication β€” determine the sender's identity
  • Authorization β€” grant rights based on verified identity
  • Example: JWT tokens, OAuth 2.0, SAML
πŸ›‘οΈ

Integrity & Confidentiality

  • Integrity β€” detect manipulation of data
  • Confidentiality β€” restrict unauthorized data access
  • Non-repudiation β€” prove authorship & validity
πŸ—οΈ

Availability & Implementation

  • Availability β€” precautions against system failure
  • Frameworks: Spring Security (Java), Django (Python)
  • Libraries: OpenSSL, Bouncy Castle
  • Security should not be retrofitted β€” design in from the start
Internationalization (I18N)

Internationalization prepares an application to support multiple locales without code changes β€” covering formatting, currency, date/time, labels, cultural differences, and more.

🌍

I18N Dimensions

Formatting Currency Cultural differences Page layout Labels Documentation Error texts Shortcuts Date / Time / TZ Fonts Application logic Technical units
πŸ”§

Implementation Tools

  • Resource Bundles β€” locale-specific key/value property files (Java)
  • i18next β€” widely used JS internationalization framework
  • Globalize β€” Unicode CLDR-based formatting
  • jQuery.I18n β€” plugin for web UIs
Communication and Messaging

Components and services must communicate β€” the communication style (sync vs. async, RPC vs. pub/sub) and technology choice are cross-cutting decisions that affect the entire system.

πŸ“‘

Communication Styles

  • Synchronous β€” caller blocks until response received
  • Asynchronous β€” fire-and-forget; callback/event-based
  • Remote Procedure Call β€” gRPC, Apache Thrift, SOAP
  • Publish/Subscribe β€” decoupled topic-based messaging
  • Broadcast β€” one sender, many receivers
⚑

Technologies

  • Message Queues/Brokers: RabbitMQ, Kafka, Amazon SQS
  • RPC Standards: gRPC, Apache Thrift, SOAP
  • API Styles: REST, GraphQL
  • Networking Protocols: HTTP, RTP, UDP Multicast, WebSockets
User Interface and Ergonomics

The UI is where users interact with the system. A cross-cutting UI strategy ensures consistent styles, responsive layouts, accessibility (WCAG), and reusable components across all platforms.

πŸ–₯️

User Interface

  • Graphical, textual, voice-control interfaces
  • Can be adaptive and/or adaptable (device, user, situation)
  • Platforms: Mobile (iOS/Android), Desktop, Web, Touch monitors
  • Responsive UIs β€” pages rearrange based on screen size
  • Consistent stylesheets, reusable UI elements
  • WCAG β€” Web Content Accessibility Guidelines
🎨

Ergonomics

  • Optimize usability and user experience
  • Interaction flow and design
  • Reactivity (perceived performance)
  • Availability, protection against user errors
  • Learnability, self-descriptiveness
  • Adaptive design β€” different layouts per platform
Business Rules and Processes

Domain-specific business rules like "if process X is finished, then do Y" are often distributed across many components. This makes them hard to find, hard to change, and expensive to maintain. A centralized approach is better.

🚫

The Problem with Hard-Coded Rules

  • Domain conditions scattered across many components β†’ low maintainability
  • Complex domain knowledge embedded in code β†’ confusing code
  • Simple changes can become expensive β†’ hard to estimate effort
  • Testing is difficult β€” business logic intertwined with infrastructure
Better: Centralized, explicit declaration using BRMS or workflow engines.

Drools β€” Business Rules Engine

Drools BRMS β€” Centralized business rules
package com.example.rules; import com.example.PurchaseOrder; rule "Apply 10% discount" when $order: PurchaseOrder(amount > 100) then $order.setDiscount( $order.getAmount() * 0.10 ); System.out.println("Applied 10% discount"); end
Uber Cadence β€” Workflow Engine
public void processOrder(String orderId) { activities.validateOrder(orderId); activities.chargePayment(orderId); activities.updateInventory(orderId); }
Cross-Cutting Concerns β€” Architecture Impact
πŸ“–

Definition

  • Aspects of the architecture affecting multiple components or the entire system
  • Usually cannot be easily separated into independent components
  • Often arise from fulfilling quality requirements
⚑

Challenge

  • Impact the entire system, adding technical constraints
  • May violate DRY/SoC if not managed
  • Can scatter code across different components
  • Hard-coded rules reduce maintainability
πŸ› οΈ

Solution Approaches

  • AOP β€” weave cross-cutting logic at compile/runtime
  • Frameworks β€” Spring Security, Hibernate, i18next
  • BRMS β€” Drools for centralized business rules
  • Workflow Engines β€” Camunda, Uber Cadence
πŸ“‹ Chapter 3 β€” Summary
  • Cross-cutting concerns β€” Architecture aspects that affect multiple components: persistence, error handling, security, I18N, communication, UI/UX, business rules
  • Persistence β€” Decouple domain from DB via ORM frameworks (Hibernate, Entity Framework, SQLAlchemy)
  • Error Handling β€” Uniform, predictable responses; differentiate logical vs. technical errors; facilitate diagnosis
  • Security β€” Authentication, authorization, integrity, confidentiality β€” design in from the start, never retrofit
  • I18N β€” Prepare for multiple locales (formatting, currency, date/time, labels) via resource bundles and I18N frameworks
  • Communication β€” Choose sync/async, RPC/pub-sub, REST/GraphQL β€” the style affects the entire system
  • UI & Ergonomics β€” Consistent styles, responsive layouts, WCAG accessibility, reusable components
  • Business Rules β€” Centralize with BRMS (Drools) or workflow engines (Camunda, Cadence) instead of scattering across code
  • Solution Approaches β€” AOP, dedicated frameworks, BRMS, and workflow engines keep cross-cutting concerns manageable
πŸ“š Additional Resources
C / C++ Macros

C/C++ macros are fragments of code that have been given a name. Whenever the name is used, it is replaced by the contents of the macro.

gcc.gnu.org/onlinedocs/cpp/Macros.html
Java Emitter Templates

JET β€” Java Emitter Templates for generating Java source code from templates.

eclipse.org β€” JET Tutorial
Jakarta Server Pages

Jakarta Server Pages (formerly JavaServer Pages) β€” server-side templating for Java web applications.

wikipedia.org β€” Jakarta Server Pages
Thymeleaf

A modern server-side Java template engine for both web and standalone environments.

thymeleaf.org
Apache FreeMarkerβ„’

A Java library to generate text output (HTML, e-mails, config files, source code) based on templates and changing data.

freemarker.apache.org
C++ Web Toolkit (Wt)

A web GUI library written in modern C++ for building interactive web applications.

webtoolkit.eu/wt
Java Web Toolkit (JWt)

A web GUI library in pure Java β€” write UI code once, run in the browser.

webtoolkit.eu/jwt
TypeScript

TypeScript is JavaScript with syntax for types β€” adds compile-time type safety to JS.

typescriptlang.org
React Native

An open-source UI framework for building Android, iOS, macOS, Windows, and Web apps from one codebase.

reactnative.dev
Flutter

An open-source UI SDK to develop cross-platform apps (Web, Android, iOS, Linux, macOS, Windows) from a single Dart codebase.

flutter.dev
gRPC

A cross-platform, open-source, high-performance remote procedure call (RPC) framework originally developed at Google.

grpc.io
GraphQL

A data query and manipulation language for APIs β€” lets a client specify exactly the data it needs. A server can fetch from multiple sources and return a unified result.

graphql.org
Summary β€” All Chapters at a Glance
01 Β· PL Features & Paradigms

Reduce Complexity via Language

  • Leverage generics, annotations, lambdas & type systems
  • Macros & code generation eliminate boilerplate
  • Paradigms (OOP, FP) shape how you decompose problems
02 Β· Annotations & AOP

Aspect-Oriented Programming

  • Annotations add declarative metadata to code
  • AOP weaves cross-cutting logic (logging, security) at compile/runtime
  • Keeps business logic clean & focused
03 Β· Cross-Cutting Concerns

Architecture-Wide Aspects

  • Persistence, error handling, security, I18N, communication
  • Design security in from the start β€” never retrofit
  • Centralize rules via BRMS / workflow engines