Skip to content

Botanical SaaS — demo pack

This page assembles sections relevant to demos and stakeholder presentations: overview, role, architecture, decisions, roadmap, and demonstration.

Contents

Summary

Status

MVP / pilot run

Role

Co-founder, technical architecture owner, system designer

Stack

Java 21, Spring Boot, PostgreSQL/PostGIS, MinIO/S3-compatible storage, Angular, OpenLayers, Docker

Project value

For the target audience (botanical gardens, nurseries, plant breeders, holders of scientific collections and private plant collections):

  • transition from disconnected Excel/local databases to a unified web platform;
  • a unified taxonomic layer as the foundation for comparable collection data;
  • managed data disclosure, public pages, and QR scenarios;
  • a foundation for network exchange scenarios between organizations.

For the professional profile:

  • designing and implementing SaaS from domain idea through MVP and pilot launch;
  • working with multi-tenancy, RBAC, domain data model, API, GIS, AI-assisted import, public data preparation, and legislative compliance (152-FZ);
  • deliberate and economical use of AI without delegating architectural decisions to the model.

What this demonstrates

This project demonstrates my ability to:

  • work at the intersection of system analysis, system design, implementation, and deployment.
  • take an idea through to a running system with multiple availability zones and a scaling roadmap
  • strategically plan implementation of a small-to-medium-scale SaaS
  • safely and in a controlled way apply LLM code generation for feature implementation

Overview

Botanical SaaS is a multi-tenant SaaS platform for botanical gardens, nurseries, plant breeders, scientific collections, and private plant collections.

The system replaces disconnected Excel files, local databases, and legacy desktop tools with a unified web platform for managing living plant collections, scientific taxonomy, geospatial data, public organization pages, QR labels, import, lists, and controlled data exchange.

My responsibility included domain modeling, backend architecture, access model design, data structure and API design, technical documentation, backend implementation, and preparing the system for cloud-ready deployment.

Role and Responsibilities

My Role

I acted as a co-founder and technical owner of the system architecture.

My work included:

  • translating domain expert knowledge into a structured SaaS model;
  • designing backend architecture, API, data structures, and domain model;
  • implementing backend functionality and integration logic;
  • designing RBAC with organization (tenant) context access rights;
  • modeling plant instances, taxonomy, places, lists, photos, imports, and public pages;
  • preparing architecture documentation, C4 diagrams, and ADRs;
  • aligning technical decisions with a domain expert;
  • using AI-assisted development tools to accelerate routine implementation while keeping manual control over architecture, data model, API contracts, review, and deployment decisions.

AI Usage

The project was developed with AI assistance.

LLMs were used to accelerate routine implementation, generate boilerplate code, and iterate quickly. Key decisions remained under manual control:

  • requirements interpretation;
  • domain modeling;
  • architecture decisions;
  • data boundaries;
  • access model;
  • code review;
  • debugging;
  • deployment decisions;
  • technical documentation.

Architecture and Integrations

Architecture

Container diagram (C4 Container)

    C4Container
    title Container Diagram for Botanical SaaS MVP

    Person(user, "User")

    System_Boundary(sys, "System") {
      Container(spa, "Single Page Application", "Angular 21, OpenLayers", "User interface") 
      Container(static, "Frontend Static", "nginx", "Container for storing<br/> static frontend files")
      Container(backend, "Backend Business Logic", "Spring Boot", "Business logic")
      ContainerDb(reldb, "Relational DB", "PostgreSQL + PostGIS", "Plant instances, lists,<br/> users, RBAC roles")
      ContainerDb(objStore, "Object Storage", "MinIO", "Photos and multimedia.<br/> Collection import files")
    }

    Boundary(ext, "External systems", "") {
      Container_Ext(global, "global", "Taxonomic reference")
      Container_Ext(vernacular, "WikiData", "Vernacular names reference<br/> for plants")
      Container_Ext(llm, "LLM", "Intelligent species<br/> recognition")
    }

  Rel(user, spa, "Uses to<br/> manage plant collections")

  Rel(spa, static, "Gets Angular<br/> static UI bundles", "HTTPS")
  Rel(spa, backend, "Sends API calls", "HTTPS REST")

  Rel(backend, reldb, "Reads/writes data", "SQL")
  Rel(backend, objStore, "Uploads/reads media", "S3 API")

  Rel(backend, global, "Requests taxa/cultivars,<br/> writes cultivars", "HTTPS/REST")
  Rel(backend, vernacular, "Gets species names<br/> in national languages", "HTTPS/REST")
  Rel(backend, llm, "Uses LLM API<br/> for species name suggestions", "HTTPS/OpenAI API compatible")

  UpdateLayoutConfig($c4ShapeInRow="5", $c4BoundaryInRow="3")

Integration Flows

Taxonomy catalog import

The system includes a manual mechanism for updating the internal taxon reference from an XLS export while preserving identifiers.

National taxon name enrichment

The system includes an automatic mechanism for enriching the internal vernacular plant names reference from open sources, subject to public API constraints.

Smart Import

The platform includes an XLS import wizard for migrating existing plant collections into the system.

The flow supports file upload, sheet selection, column mapping (including with AI assistance), value resolution, fuzzy matching, asynchronous processing, row-level results, and Excel export of erroneous rows.

For mapping column names to system entity attributes, and for more accurate recognition of species, cultivar, or enum values, LLM integration and a lightweight harness are provided. Recognition confirmation (when it was not 100%) is performed by the user.

Decisions, Trade-offs, and Risks

Key Decisions

Decoupled frontend and backend

The system uses a separate Angular frontend and Spring Boot backend API. This reduces coupling, allows independent evolution of UI and backend logic, and creates a foundation for future client channels.

Root-unit soft multi-tenancy

Each tenant is represented by a root organizational unit. Tenant-scoped entities carry root_unit_id, and access is constrained through repositories, specifications, services, and API-level checks.

Context-aware authorization

Permissions depend not only on the user's global role, but also on the current organization and selected organizational context.

This fits B2B scenarios where the same user may have different permissions in different organizations or departments.

PostGIS as part of the domain model

Plant locations, garden areas, greenhouses, beds, and polygons are modeled as spatial data, not as a secondary map overlay.

Hybrid current state and history model

The system separates current operational state from data related to change history and audit trail. This allows efficient work with current records while preserving traceability of important changes.

Controlled public data exposure

Public pages, plant cards, lists, photos, and map data are published through separate public endpoints and DTOs.

Visibility rules prevent accidental exposure of internal tenant data.

Architectural Trade-offs

1. Soft multi-tenancy instead of full isolation

Context

The system is designed for many organizations (multi-tenant). At an early stage the product needs low operational complexity, high development speed, and meeting functional requirements (taxonomic layer and ability to develop network scenarios between organizations).

Decision

Logical multi-tenancy through a root_unit_id attribute is used for data isolation. The root organizational unit is the organization boundary; all entities in the tree reference this root unit.

Rejected alternative

database-per-tenant or schema-per-tenant.

Rationale
  • lower infrastructure complexity at an early stage
  • allows absorbing organizations or spinning off departments with minimal entity migration
Trade-offs
  • Data isolation is logical, not physical; any tenant filtering error in any API can break tenant isolation.
  • Harder to back up, restore, export, and physically delete data for a single organization (right to be forgotten).
  • Harder to dedicate a large client to separate infrastructure.
  • Sharding and regional data separation will require additional design.
Compensating measures
  • All entities belonging to an organization carry root_unit_id.
  • Access is constrained at multiple levels: repository queries, JPA specifications, business-logic checks, controller method authorization.
  • Central authorization is extracted into AccessControlService.
  • Public APIs return only public DTOs and do not expose internal fields.
  • Integration tests for cross-tenant access denial are written for critical scenarios.
  • For 152-FZ compliance a separate availability zone for RF users is provisioned (two zones so far). Replication of public data between availability zones is planned, accounting for possible disconnection of the RF network segment from the global internet.
Review trigger
  • enterprise clients requiring physical isolation
  • data volume growth to noisy-neighbor levels
  • need for regional data storage or legal requirement to physically separate organization data

2. Modular monolith instead of microservices

Context

The product has a broad domain model: plants, taxonomy, cultivars, lists, places, import, public pages, users, roles, media, and inter-organization exchange. The team is small and the domain model is still actively refined. It is also unclear how much load will differ across entities.

Decision

The backend is implemented as a modular monolith on Spring Boot: a single artifact with separation by domain areas through controllers, services, repositories, and DTOs.

Rejected alternative

A set of microservices: taxonomy service, collections service (+ import), media service, authorization and identity service, GIS service, public resources service.

Why this makes sense now

A monolith reduces distributed-system overhead: no network contracts between services, no distributed transactions, no service discovery infrastructure, no complex observability or inter-service failure orchestration. This speeds delivery and keeps the domain model cohesive while the product has not yet passed the first pilots.

Trade-offs
  • Individual domain modules cannot be scaled independently.
  • A failure in one module can affect the entire backend.
  • Over time, implicit dependencies between domain areas may emerge.
  • Import, media, and GIS may have different load profiles but still live in one application.
Compensating measures
  • Strict package separation by domain areas. Service layer as the boundary for business logic, DTOs as the API boundary. This lowers the cost of extracting a bounded context into its own service.
  • Asynchronous execution of heavy imports to avoid blocking a thread and hitting timeouts.
Review trigger

Service extraction makes sense when a specific module gets independent load scale, a dedicated owning team, a separate release cadence, or separate resilience requirements. First extraction candidates: import pipeline, media processing/storage gateway, public map/search read model.


3. Monorepo for backend and frontend instead of separate repositories

Context

The project is developed by a small team where one developer owns architecture, backend, frontend, deployment, and integration. For these conditions, speed of coordinated changes matters more than organizational independence of teams.

Decision

Backend and frontend are stored in one repository.

Rejected alternative

Separate repositories for backend, frontend, infrastructure, and documentation.

Why this makes sense now

A monorepo allows atomic API and UI changes, easier maintenance of full architectural context, faster full-stack refactoring and LLM code generation. The result is more stable because the model sees the connected product picture.

Trade-offs
  • Responsibility boundaries may blur as the team grows.
  • Harder to restrict access to parts of the codebase.
  • Higher risk of broad changes without understanding blast radius.
Compensating measures
  • Separate frontend/backend folders and independent build and run commands.
  • Integration tests for frontend/backend interaction.
Review trigger

Separate repos become justified when independent teams with different release cycles appear, different code access policies, or need to publish parts of the system independently.


4. Docker Compose instead of cloud native

Context

At an early stage the system must deploy quickly on a VPS, demonstrate the product, run pilots, and keep infrastructure costs low.

Decision

Backend, frontend, PostgreSQL/PostGIS, and MinIO run in a single Docker Compose environment.

Rejected alternative

Full cloud-native infrastructure with dedicated DB and object storage capacity, container autoscaling, monitoring, and multi-zone deployment.

Why this makes sense now

Docker Compose gives fast cold start, reproducible environment, low cost, and a simple operational model. For MVP, demo stand, and early pilot this is better than premature cloud-native complexity.

Trade-offs
  • A single VPS is a single point of failure.
  • DB, object storage, and services compete for resources.
  • Scaling is mostly vertical.
  • No full high-availability model, no peak load handling. Cannot be considered production architecture.
  • Backup, restore, and monitoring become critical operational tasks.
Compensating measures
  • Services remain stateless.
  • Configuration must be env-based.
  • DB and object storage data live on persistent volumes with regular backups.
  • Reverse proxy / TLS / rate limits are moved to the infrastructure layer.
  • Target migration path must be described in advance: separate PostgreSQL/PostGIS instances, S3-compatible storage, horizontal scaling of service containers.
Review trigger

Transition is needed when paying clients appear, SLA expectations, growth in photo/import volume, high-availability requirements, regular VPS downtime, or need for regional data placement.

... Only part of the trade-offs is published in the portfolio.

See also Architecture Decision Records.

Roadmap and Demonstration

Roadmap

Phase Goal Infrastructure Exit criteria
MVP / demo demonstrate a working system and core scenarios VPS + Docker Compose demo flow, backup, basic observability
Pilot load real data and collect feedback VPS + regular backup procedures; migrate to external S3 as media grows import of a real collection, list of UX/data issues
First customers ensure predictable operations separate DB, external S3-compatible object storage, monitoring paying organization, recovery procedure, support
Growth prepare scaling and regional contours separate DB, external S3-compatible object storage, separate workers (import), read optimization, regional strategy tenant count growth, SLA expectations, regional tenant distribution

Screenshots and Demo

Global map

UI_1

Global map

Plant management

UI_2

Plant management

Places and boundaries management

UI_3

Places and boundaries management

AI-assisted plant import

UI_4

AI-assisted plant import

What this project demonstrates

This project demonstrates my ability to:

  • work at the intersection of system analysis, backend design, implementation, and deployment.
  • take an idea through to a running system with multiple availability zones and a scaling roadmap
  • strategically plan implementation of a small-to-medium-scale SaaS
  • safely and in a controlled way apply LLM code generation for feature implementation