Second Place — DIVEC Project Exposition 2019B. SinPluma is a SaaS writing platform (authors can write, edit, publish and read works). This review focuses on the architecture: microservices, libraries used, inter-service communication, and the automated InnoDB Cluster that provides the transactional backbone.
Overview
SinPluma is a web application whose user-facing feature set is straightforward: authenticated writers create and edit works (notebooks/pages), upload images, follow other authors, and read published content. The UI is a single-page React app with a Slate-based rich editor; the backend exposes an API for all product functionality.
Where SinPluma becomes interesting — and where this post concentrates — is in its architecture and ops: the project was built as an API-first, containerized system with:
- an API layer (Flask-based service),
- a small auxiliary “linguistics” service for text analysis,
- object storage (MinIO) for media,
- Redis for session/token state,
- an InnoDB Group Replication (MySQL) cluster managed and bootstrapped by a MySQL Shell script,
- an Nginx edge that routes traffic to the SPA and API.
This review explains what every component does, the libraries used, how services interact, how the InnoDB cluster is configured and bootstrapped, and concrete suggestions to evolve the system toward production-readiness.
High-Level Goals
From the repository and deployment artifacts, the project pursued these goals:
- API-first UX — every capability is exposed via JSON/HTTP so web/mobile clients or third parties can integrate.
- High availability for critical state — editorial content, user accounts and billing are persisted in a replicated InnoDB cluster rather than a single DB instance.
- Loose coupling — separation of concerns (API, media, linguistics) and distinct execution contexts via containers.
- Operational automation — database cluster creation and initial SQL loading are scripted to allow repeatable bootstraps in a Docker Compose environment.
- Simple, pragmatic engineering — stack choices favor rapid development and clear ownership (Flask for API, React+Slate for editor).
System Architecture
At a glance, the production-local stack (indocker-compose.yml) contains the following logical components. I list each microservice / component, the language and libraries used (as found in the code / requirements), and the responsibility it implements.
API / Application (flaskService)
- Purpose: The canonical backend API — authentication, user profiles, notebooks (works), pages, readings, search endpoints, image upload integration with MinIO and token blacklist management in Redis.
- Language: Python 3.8 (Flask).
- Core Libraries (from
requirements.txt):Flask 1.1.2,Flask-RESTful 0.3.8(routing / REST patterns)Flask-JWT-Extended 3.25.1(JWT-based auth)Flask-SQLAlchemy 2.5.1,pymysql 0.10.0(DB access)marshmallow,flask-marshmallow(serialization / validation)Flask-Minio(MinIO integration)flask-redis(Redis connection),bcrypt(password hashing)gunicornused in container entrypoint
- What it delivers:
- CRUD for users, notebooks (works), pages, readings, genres.
- Token issuance & blacklist checks (stored in Redis).
- Image upload/download API that stores images to MinIO.
- Simple search endpoint implemented with SQL LIKE queries (no separate search engine).
- Notes: The service connects to the MySQL logical endpoint via SQLAlchemy URI configured to talk to
inno_router:6446(MySQL Router).
Linguistics service
- Purpose: Lightweight NLP endpoints (used by the frontend for sentiment per-sentence); proxied by Nginx under
/api/lin/. - Language: Python / Flask.
- Core Libraries (from
linguistics/requirements.txt):Flask 1.1.2,Flask-RESTfulscikit-learn 0.23.1(ML utilities for sentiment / text features)
- What it delivers: HTTP endpoints consumed by the SPA when the editor requests sentence-level sentiment. The frontend calls
/lin/sentimentwhich is proxied to this service.
Front-end (reacts)
- Purpose: Single-page application and editor where writers author content and readers explore published works.
- Language / Stack: React (16.x), Redux, Slate editor.
- Core libraries (from
package.json):react,react-dom,react-redux,react-router-domslate,slate-react(rich-text editor)@material-ui/core(UI),axiosfor API callsredux-thunkfor async actions
- What it delivers: Editor UI (Slate), authentication flows, upload hooks for images, ajax calls to API and linguistics endpoints, and optimistic UI patterns.
MinIO (object storage)
- Purpose: S3-compatible private object store for binary assets (profile images, embedded images for works).
- Image behavior: Flask uses Flask-Minio to PUT/GET objects to
minioservice (local Docker host mapping 9000).
Redis
- Purpose: Lightweight in-memory store used for:
- token blacklist / revoked JWT JTIs,
- caching primitives (used by
Flask-Redis), - potential temporary state.
- Image: official
redis:alpineindocker-compose.
Nginx (edge / API gateway)
- Purpose: Single public entry point. Responsibilities:
- route
/api/lin/→ linguistics service, - route
/api/→ flask service, - serve SPA root and proxy WebSocket / upgrade headers as needed,
- terminate TLS (in a production-intended flow).
- route
- Notes: The
nginx/nginx.confrewrites paths and proxies to internal container names as shown in the compose topology.
InnoDB Cluster (MySQL Group Replication)
- Purpose: Transactional, highly-available relational backend for users, content, versions and other authoritative data.
- Components present in repository:
- Three
mysql/mysql-server:8.0.*nodes (in Docker Compose:inno_server_01,_02,_03) with per-node CNF files (innodbCluster/inno_server_0*.cnf). - A
mysql/mysql-routercontainer (inno_router) that presents logical endpoints to application services. - A
mysqlshhelper container that runscreate_cluster.pyto bootstrap/repair the InnoDB cluster.
- Three
- What it delivers: Group Replication with automatic primary election and the MySQL Router logical endpoints the application connects to.
How the system actually works (infrastructure, interaction & InnoDB cluster automation)
This section is a curated, implementation-oriented description of the runtime topology, message flows and how the InnoDB cluster is created and used by the application.
Request flows and service interaction (end-to-end)
Client → Nginx (Edge)
- Browser/mobile clients hit the Nginx public port. Nginx proxies:
GET /and SPA static assets →reactcontainer/api/paths →flaskcontainer (the primary API)/api/lin/→linguisticscontainer
- Nginx also forwards upgrade headers for WebSocket if needed.
- Browser/mobile clients hit the Nginx public port. Nginx proxies:
Synchronous CRUD operations (Flask)
- The React app saves drafts, updates pages, or publishes content by POST/PUT/GET calls to Flask endpoints (
/api/notebooks,/api/pagesetc). - Flask uses SQLAlchemy (
mysql+pymysql) to perform transactions against the InnoDB cluster logical endpoint exposed by MySQL Router (inno_router:6446).
- The React app saves drafts, updates pages, or publishes content by POST/PUT/GET calls to Flask endpoints (
Media handling
- File uploads (profile images, images embedded in content) are routed to the Flask image endpoint; Flask uses Flask-Minio to store objects in the MinIO container and then persists the object reference in MySQL.
Linguistics calls
- The editor collects sentence fragments and calls
/api/lin/sentiment?sentence=...(front-end uses Axios). Nginx rewrites and forwards the request to the linguistics service which returns a sentiment label that the UI then shows inline.
- The editor collects sentence fragments and calls
JWT & session state
- Flask-JWT-Extended issues tokens. Revoked token JTIs are stored in Redis (the project uses
flask-redisto persist these keys andJWT_BLACKLIST_ENABLEDto enforce revocation checks).
- Flask-JWT-Extended issues tokens. Revoked token JTIs are stored in Redis (the project uses
InnoDB Cluster provisioning and automation
The repository contains a small but thorough automation approach to configure an InnoDB Group Replication cluster within the Docker Compose environment:
- Per-node config: Each MySQL node has a dedicated config file under
innodbCluster/inno_server_0*.cnf. Those CNF files enable Group Replication settings (GTID mode,transaction_write_set_extraction,log_slave_updates,relay_log, uniqueserver_id, andreport_host). These settings prepare each node to join group replication. - Data persistence: Compose mounts
./innodbCluster/data/inno_server_0X:/var/lib/mysql— persistent mounts for node data. - Bootstrap script: A helper container
inno_shellrunsmysqlsh --py --file=/create_cluster.pyafter the MySQL servers are available. The Python script (create_cluster.py) uses the MySQL Shell (dbaobject) API to:- configure instances (
dba.configure_instance(...)), - create the cluster (
dba.create_cluster(...)) when needed, - add instances with recovery options,
- recover a cluster if it detects an outage/dead cluster, and
- wait for Group Replication readiness before proceeding.
- configure instances (
- Router: The
mysql-routerimage is configured and used to provide a single logical endpoint; the Flask app connects to the Router instead of connecting to individual MySQL hosts. The router configuration allows the application to use a singlehost:portwhere Router internally dispatches to the cluster members. - Initial data load: The entrypoint script for the shell waits for each server to respond, runs the
create_cluster.pyscript to initialize the InnoDB cluster, and (if present) executes the SQL fileinnodbCluster/sinpluma.sqlto load bootstrap schema/data. - Connection in the app: Flask’s
SQLALCHEMY_DATABASE_URIpoints tomysql+pymysql://root:root@inno_router:6446/SinPluma?charset=utf8mb4— the router port the app uses for reads/writes; this makes DB failover and routing transparent to SQLAlchemy.
Observability & deployment notes in repo
- Containers: The system is set up with Docker Compose to run all services locally or in a single-host environment.
- Automation tools: The cluster bootstrap relies on the MySQL Shell scripting APIs. The repo contains convenience scripts and docker definitions to bring services up in a deterministic order.
- Caveats (as surfaced in the repository): a repository README note warns that the system does not automatically “see” changes if the
innodbCluster/datafolder is modified externally — an artifact of how Compose snapshots and container volumes interact.
Engineering Decisions & Design Tradeoffs
The codebase and the deployment layout reveal explicit design choices. I summarize the tradeoffs and why they were reasonable given the project goals.
Single logical API (Flask) vs fully-decomposed microservices
- Decision: The project uses a single Flask service to implement most domain logic (users, notebooks, pages, readings, image handling) and a second small service for linguistics.
- Rationale: Keeps inter-service complexity low while still separating a computationally different concern (NLP) into its own container.
- Tradeoff: The monolithic API is easier to reason about and test, but it reduces the operational independence and per-service scaling granularity that a full microservices split provides.
Use of an InnoDB Group Replication cluster (MySQL) + Router
- Decision: Use MySQL Group Replication for availability and MySQL Router for a unified endpoint.
- Rationale: Editorial content and user accounts require transactional integrity; Group Replication provides replication with failover semantics appropriate for that need.
- Tradeoff: Group Replication adds operational complexity (configuration, bootstrapping, backups, recovery). The repo addresses this with scripting, but it requires more ops knowledge than a single-instance DB. Using Router hides cluster complexity from the application.
No external message broker / asynchronous pipeline
- Observation: The codebase has no Kafka/RabbitMQ/Redis Streams usage for domain events (the search endpoint is implemented with SQL LIKE). All flows are synchronous.
- Tradeoff: Synchronous flows simplify development and reduce the number of moving parts, but they limit scalability for background indexing, notification batching and long-running tasks. For example, search indexing and notifications would benefit from an event-driven pipeline.
Simplicity-first search & NLP strategy
- Decision: Search uses SQL
LIKE(seeNotebook.find) and sentiment uses a small Flask + scikit-learn service. - Rationale: For MVP-level content discovery this is acceptable and simple to maintain.
- Tradeoff: SQL LIKE does not scale or provide relevance tuning of a dedicated search engine (Elasticsearch/OpenSearch). The linguistics service is a pragmatic add-on, but for large volumes a dedicated indexing + NLP pipeline would be more robust.
Local-first container orchestration (Docker Compose)
- Decision: The repository uses
docker-composeto define all services and the InnoDB cluster bootstrap. - Tradeoff: Docker Compose is excellent for local reproducibility and demos (DIVEC exposition), but in production you would move to Kubernetes or managed services for better orchestration, scaling and upgrade semantics. The repo’s cluster automation, however, demonstrates strong operational thinking even for a Compose-based environment.
Why This Project Matters
For a technical recruiter or engineering manager, SinPluma signals several strengths:
- Practical full-stack skillset: spanning SPA front-end (React + Slate), REST API design, storage/transactional design (MySQL Group Replication), and object storage (S3-compatible MinIO).
- Operational awareness: the automated cluster bootstrap via MySQL Shell + Router shows the author understands production concerns: cluster configuration, initialization, and schema bootstrapping are scriptable and repeatable.
- Security hygiene in the app: JWT-based auth, token blacklist pattern via Redis, password hashing with bcrypt, and TLS termination planned at the Nginx edge reflect attention to authentication and session security.
- Sensible separation of concerns: moving heavier NLP computation into a dedicated service keeps the main API lightweight and focused on transactional behavior.
- Recognition & impact: the project gained DIVEC recognition and direct titulación, indicating the system achieved a working, demonstrable standard and met contest evaluation criteria.
Improvements
Introduce an event broker for async work
- Add RabbitMQ/Kafka to offload search indexing, email/notification delivery, analytics, and long-running image processing. This lets the API return quickly and scale consumers independently.
Adopt a proper search index
- Replace SQL LIKE search with Elasticsearch/OpenSearch for relevance, faceting and performance at scale.
Database lifecycle operator or managed service
- Migrate InnoDB cluster provisioning to a MySQL Operator in Kubernetes or use a managed DB offering. That reduces operational burden while retaining HA semantics.
Migrations & schema management
- Integrate Alembic (or Flyway) for explicit, versioned schema migrations executed in CI/CD rather than ad-hoc SQL loads.
Stronger integration testing & CI
- Add tests that boot the compose environment (or lightweight mocks) to validate end-to-end flows: image upload, DB transactions, JWT flows, and linguistics integration.
Secrets & configuration
- Move away from plaintext
.envfiles in production; use a secrets manager (Vault or KMS-backed secrets in the cloud) integrated with the runtime.
- Move away from plaintext
Observability improvements
- Add metrics and tracing instrumentation inside Flask and the linguistics service (Prometheus + OpenTelemetry + Jaeger) so production issues are triaged faster.
Scaling the editor & real-time collaboration
- If live collaboration is desired, add a dedicated real-time service (OT/CRDT) or use a hosted provider; current Slate-based client supports local editing but not out-of-the-box multi-user sync.
Closing Thoughts
SinPluma is an excellent example of an engineering-minded student project that intentionally reaches beyond a simple CRUD app:
- It pairs a practical, developer-friendly API and SPA with operational automation for a multi-node database cluster.
- The project shows a clear appreciation for reliability and data correctness (InnoDB Group Replication, Router), and pragmatic tradeoffs (no immediate event-broker complexity; simple SQL search) that match an MVP-to-demo timeline.
- The code reveals solid choices in libraries and integration points (Flask + SQLAlchemy + MinIO + Redis), and the cluster-bootstrapping scripts demonstrate readiness to operate the system beyond “works on my laptop.”
This project signals: engineering breadth (front-end, back-end, infra), ops capability (cluster automation), and product sense (editor-first UX). With a few additions around asynchronous pipelines, search indexing and managed DB operations, SinPluma would be well positioned to move from an award-winning demo into a production SaaS offering.
