Tu Cielo de Noche
#website link Tu Cielo de Noche
Mapping the Cosmos: Building a Microservice-Driven Celestial Map Generator
Introduction
Ever looked up at the night sky and wondered what the stars looked like on a specific date and time from a particular location? That's the question that sparked the creation of "Tu Cielo de Noche," a web application that transforms celestial data into personalized, printable star maps. This project wasn't just about pretty pictures; it was a deep dive into microservices, serverless architecture, and the intricacies of astronomical calculations. Join me as I share the journey of building this unique e-commerce experience.
The Vision: Personalized Celestial Maps
"Tu Cielo de Noche" empowers users to generate a stunning stereographic representation of the night sky, capturing the precise positions of stars, planets, constellations, the moon's phase, and the sun, all based on a user-defined date, time, and location. The core of this application lies in its ability to translate complex astronomical data from the Hipparcos catalog (stored as a JSON file) into a visual masterpiece using the powerful astronomy.js
library for the calculations.
Tech Stack: A Microservices Approach
To bring this vision to life, I assembled a robust tech stack:
- Next.js: For a dynamic and performant frontend and API endpoints.
- TypeScript: Ensuring type safety and code maintainability.
- Tailwind CSS: For rapid and responsive UI development.
- Supabase: Providing a reliable database and image storage solution.
- Canvas.js: For high-quality image rendering.
- Stripe: Handling secure payment processing.
- Resend: Sending automated order confirmation emails.
- Prodigi API: Integrating print-on-demand services for physical posters.
- AWS SQS/Lambda: Orchestrating background processing and job execution.
- Docker: For containerizing and deploying the Lambda function.
The Challenge: Consistent Image Rendering
One of the biggest hurdles was ensuring consistent image quality and dimensions across all orders. Client-side rendering, while convenient, was limited by the user's screen resolution and DPI. To overcome this, I implemented a dual-rendering approach:
- Client-Side: For real-time previews and user interaction.
- Server-Side: For generating high-resolution, print-ready images.
This meant duplicating the celestial map processing logic, but it was essential for maintaining quality.
The Workflow: Payment-Triggered Processing
To optimize resource usage and ensure that map generation only occurred for successful orders, I integrated Stripe webhooks. When a payment was successful, Stripe triggered a webhook event, which in turn placed a message containing the order ID and celestial map configuration onto an AWS SQS queue.
Here's a simplified example of how the Stripe webhook processes successful payment events:
// Simplified Stripe Webhook Example
import Stripe from "stripe";
// Initialize Stripe (replace with your test secret key for example)
const stripe = new Stripe("sk_test_example", {
apiVersion: "2023-10-16",
});
// Example webhook secret (replace with a test secret)
const webhookSecret = "whsec_example";
export async function handler(req: Request) {
try {
const body = await req.text();
const signature = req.headers.get("stripe-signature");
if (!signature) {
console.error("No Stripe signature found.");
return new Response(JSON.stringify({ error: "No signature" }), {
status: 200,
});
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
console.error("Webhook signature verification failed:", err);
return new Response(JSON.stringify({ error: "Verification failed" }), {
status: 200,
});
}
if (event.type === "payment_intent.succeeded") {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
const orderId = paymentIntent.metadata?.orderId;
if (orderId) {
console.log(`Payment succeeded for order: ${orderId}`);
// Simulate adding job to queue (replace with your queue logic)
await addToQueue(orderId, paymentIntent.metadata);
} else {
console.error("No orderId found in payment intent metadata.");
}
}
return new Response(JSON.stringify({ received: true }), { status: 200 });
} catch (err) {
console.error("Webhook processing error:", err);
return new Response(JSON.stringify({ error: "Processing error" }), {
status: 200,
});
}
}
// Example function to simulate adding a job to a queue
async function addToQueue(orderId: string, metadata: any) {
console.log(
`Simulating adding order ${orderId} to queue with metadata:`,
metadata
);
// Replace this with your actual queue integration logic.
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate queue delay
console.log(`Order ${orderId} added to queue.`);
}
An AWS Lambda function, acting as a worker, then consumed these messages and executed the server-side celestial map processor. This ensured that map generation was tightly coupled with payment success.
The Lambda Function: Processing SQS Events
The core of our server-side processing is an AWS Lambda function that handles messages from the SQS queue. This function is triggered whenever a new message arrives, signaling a successful payment and the need to generate a celestial map.
Here's a simplified example of the Lambda function's core logic:
// Simplified Lambda Function Example
import { SQSEvent, Context } from "aws-lambda";
/**
* Example function to process SQS messages.
* This simulates the core logic of your celestial map processing.
*/
export async function handler(
event: SQSEvent,
context: Context
): Promise<void> {
console.log("Processing SQS event...");
// Iterate through each record in the SQS event
for (const record of event.Records) {
try {
// Parse the JSON message body
const messageBody = JSON.parse(record.body);
// Extract order ID and celestial map configuration
const orderId = messageBody.orderId;
const mapConfig = messageBody.mapConfig;
console.log(`Processing order ID: ${orderId}`);
// Simulate celestial map processing (replace with your actual logic)
await processCelestialMap(orderId, mapConfig);
console.log(`Order ID: ${orderId} processed successfully.`);
} catch (error) {
console.error("Error processing SQS message:", error);
}
}
console.log("SQS event processing complete.");
}
/**
* Example function to simulate celestial map processing.
* Replace this with your actual map generation logic.
*/
async function processCelestialMap(
orderId: string,
mapConfig: any
): Promise<void> {
// Simulate asynchronous map generation
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate 1 second processing time
console.log(
`Simulated celestial map generation for order ID: ${orderId} with config:`,
mapConfig
);
// Here, you would use mapConfig to generate the celestial map.
// And then store the result.
}
The Lambda Deployment Saga: Docker and Manifest Compatibility
Deploying the server-side celestial map processor as an AWS Lambda function presented a unique challenge. The canvas.js
library, with its native dependencies, required a Docker image container for deployment.
However, I encountered a frustrating error: "manifest not compatible." After some research, I discovered that AWS Lambda had limitations with multi-architecture Docker image manifests. The solution was simple but crucial:
docker build --platform linux/amd64 -t celestial-map-processor:latest
By explicitly specifying the linux/amd64
platform during the Docker build process, I resolved the manifest compatibility issue. This was a significant win, and it felt incredibly rewarding to overcome this technical hurdle.
The Outcome: A Seamless Celestial Experience
With the deployment issues resolved, "Tu Cielo de Noche" began functioning flawlessly. Users could now effortlessly create personalized star maps, and the entire process, from payment to print-on-demand fulfillment, was automated.
Key Takeaways:
- Microservices architecture offers flexibility and scalability.
- Serverless computing with AWS Lambda and SQS enables efficient background processing.
- Docker containerization simplifies complex deployments.
- Attention to platform-specific details is crucial for successful deployments.
- Webhooks are a very powerful tool to interconnect services.
Building "Tu Cielo de Noche" was a challenging yet incredibly rewarding experience. It's a testament to the power of modern web technologies and the ability to turn complex scientific data into a personalized and meaningful experience.