Is 2025 the year of API-first development and democratization? Take our annual survey and tell us how you implement APIs across your org.
Integrate database deployments into your CI/CD workflows. Learn key techniques to handle the unique constraints of database operations.
Introduce a New API Quickly Using Spring Boot and Gradle
Is Vibe Coding Agile or Merely a Hype?
Generative AI
AI technology is now more accessible, more intelligent, and easier to use than ever before. Generative AI, in particular, has transformed nearly every industry exponentially, creating a lasting impact driven by its (delivered) promises of cost savings, manual task reduction, and a slew of other benefits that improve overall productivity and efficiency. The applications of GenAI are expansive, and thanks to the democratization of large language models, AI is reaching every industry worldwide.Our focus for DZone's 2025 Generative AI Trend Report is on the trends surrounding GenAI models, algorithms, and implementation, paying special attention to GenAI's impacts on code generation and software development as a whole. Featured in this report are key findings from our research and thought-provoking content written by everyday practitioners from the DZone Community, with topics including organizations' AI adoption maturity, the role of LLMs, AI-driven intelligent applications, agentic AI, and much more.We hope this report serves as a guide to help readers assess their own organization's AI capabilities and how they can better leverage those in 2025 and beyond.
Apache Cassandra Essentials
Identity and Access Management
Hi community! This is my third article in a series of introductions to Spring AI. You may find the first two on the link below: Using Spring AI to Generate Images With OpenAI's DALL-E 3Exploring Embeddings API with Java and Spring AI In this article, I’ll skip the explanation of some basic Spring concepts like bean management, starters, etc., as the main goal of this article is to discover Spring AI capabilities. For the same reason, I’ll not create detailed instructions on generating the AWS Credentials. In case you don’t have one, follow the links in Step 0, that should give you enough context on how to create one. Also, please be aware that executing code from this application may cost you some money for running AWS Bedrock. The code I will share in this article is also available in the GitHub repo. Introduction Today, we are going to implement a simple proxy application that will forward our text prompt to a foundational model managed by AWS Bedrock and return a response. This will allow us to make our code more flexible and configurable. But before we start, let me give you a little introduction to Amazon Bedrock. Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Luma, Meta, Mistral AI, poolside (coming soon), Stability AI, and Amazon through a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. The single-API access of Amazon Bedrock, regardless of the models you choose, gives you the flexibility to use different FMs and upgrade to the latest model versions with minimal code changes. You may find more information about Amazon Bedrock and its capabilities on the service website. Now, let’s start implementing! Step 0. Generate AWS Keys and Choose the Foundational Model to Use If you don’t have an active AWS access key, do the following steps (copy-pasted from this SOF thread): Plain Text Go to: http://aws.amazon.com/ Sign Up & create a new account (they'll give you the option for 1 year trial or similar) Go to your AWS account overview Account menu in the upper-right (has your name on it) sub-menu: Security Credentials After you have your keys generated you should choose and enable the foundational model in Bedrock. Go to Amazon Bedrock, and from the Model Access menu on the left, configure access to the models you are going to use. Step 1. Set Up a Project To quickly generate a project template with all necessary dependencies, one may use https://start.spring.io/. In my example, I’ll be using Java 17 and Spring Boot 3.4.1. Also, we need to include the following dependency: Amazon Bedrock Converse AI. Spring AI support for Amazon Bedrock Converse. It provides a unified interface for conversational AI models with enhanced capabilities, including function/tool calling, multimodal inputs, and streaming responsesWeb. Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container. After clicking generate, open downloaded files in the IDE you are working on and validate that all necessary dependencies and properties exist in pom.xml. XML <properties> <java.version>17</java.version> <spring-ai.version>1.0.0-M5</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bedrock-converse-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> At the moment of writing this article, Spring AI version 1.0.0-M5 has not been published to the central maven repository yet and is only available in the Spring Repository. That’s why we need to add a link to that repo in our pom.xml as well: XML <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> Step 2. Set Up the Configuration File As a next step, we need to configure our property file. By default, Spring uses application.yaml or application.properties file. In this example, I’m using the YAML format. You may reformat code into .properties if you feel more comfortable working with this format. Here are all the configs we need to add to the application.yaml file: YAML spring: application: name: aichatmodel ai: bedrock: aws: access-key: [YOUR AWS ACCCESS KEY] secret-key: [YOUR AWS SECRET KEY] converse: chat: options: model: amazon.titan-text-express-v1 Model: The model ID to use. You can use the supported models and model features. Model is the only required config to start our application. All the other configs are not required or have default values. As the main purpose of this article is to show the ease of Spring AI integration with AWS Bedrock, we will not go deeper into other configurations. You may find a list of all configurable properties in the Spring Boot docs. Step 3: Create a Chat Service Now, let’s create the main service of our application -> chat service. To integrate our application with the Chat Model managed by AWS Bedrock, we need to autowire the interface with the same name: ChatModel. We already configured all the chat options in the application.yaml Spring Boot will automatically create and configure the Instance (Bean) of ChatModel. To start a conversation with a particular prompt, we just need to write one line of code: ChatResponse call = chatModel.call(prompt); Let’s see what the whole service looks like: Java @Service public class ChatService { @Autowired private ChatModel chatModel; public String getResponse(String promptText) { Prompt prompt = new Prompt(promptText); ChatResponse call = chatModel.call(prompt); return call.getResult().getOutput().getContent(); } } That's it! We just need three lines of code to set up integration with the AWS Bedrock chat model. Isn't that amazing? Let’s dive deep into this code: As I previously mentioned, ChatModel bean is already configured by Spring Boot as we provided all the necessary info in the application.yaml file. We just need to ask Spring to inject it using Autowire annotation.We are creating a new instance of the Prompt object that provides the user with a prompt and executes the previously injected chat model. After receiving a response, we just return content, which is nothing but an actual chat response. Step 4. Creating Web Endpoint To easily reach our service, let’s create a simple GET endpoint. Java @RestController public class ChatController { @Autowired ChatService chatService; @GetMapping("/chat") public String getResponse(@RequestParam String prompt) { return chatService.getResponse(prompt); } } We are creating a new class annotated with @RestController.To reach our service, we need to inject it using field injection. To achieve this, we add our service and annotate it with @Autowire.To create an endpoint, we introduce a new method and annotate it with @GetMapping("/chat"). This method does nothing but receive a user prompt and pass it to the service we created in the previous step. Step 5. Run Our Application To start our application, we need to run the following command: mvn spring-boot:run When the application is running, we may check the result by executing the following curl with any prompt you want. I used the following: What is the difference between Spring, String, and Swing? Don't forget to add %20 instead of whitespaces if you are using the terminal for calling your endpoint: Shell curl --location 'localhost:8080/chat?prompt=What%20is%20the%20difference%20between%20Spring%2C%20String%20and%20Swing%3F' After executing, wait a few seconds as it takes some time for Amazon Bedrock to generate a response, and voila: Plain Text Spring is a framework for building enterprise-ready Java and Spring Boot applications. It offers a comprehensive set of tools and libraries to streamline development, reduce boilerplate code, and promote code reusability. Spring is known for its inversion of control (IoC) container, dependency injection, and aspect-oriented programming (AOP) features, which help manage the complexity of large applications. It also provides support for testing, caching, and asynchronous programming. Spring applications are typically built around the model-view-controller (MVC) architectural pattern, which separates the presentation, business logic, and data access layers. Spring applications can be deployed on a variety of platforms, including servlet containers, application servers, and cloud environments. String is a data type that represents a sequence of characters. It is a fundamental building block of many programming languages, including Java, and is used to store and manipulate text data. Strings can be accessed using various methods, such as length, index, concatenation, and comparison. Java provides a rich set of string manipulation methods and classes, including StringBuffer, StringBuilder, and StringUtils, which provide efficient ways to perform string operations. Swing is a graphical user interface (GUI) library for Java applications. It provides a set of components and APIs that enable developers to create and manage user interfaces with a graphical interface. Swing includes classes such as JFrame, JPanel, JButton, JTextField, and JLabel Step 6: Give More Flexibility in Model Configuration (Optional) In the second step, we configured the default behavior to our model and provided all the necessary configurations in the application.yaml file. But can we give more flexibility to our users and let them provide their configurations? The answer is yes! To do this, we need to use the ChatOptions interface. Here is an example: Java public String getResponseFromCustomOptions(String promptText) { ChatOptions chatOptions = new DefaultChatOptionsBuilder() .model("amazon.titan-text-express-v1") .topK(10) .temperature(0.1) .build(); return ChatClient.create(this.chatModel) .prompt(promptText) .options(chatOptions) .call() .content(); } To achieve this, we need to build options programmatically using all the configs we set up in application.yaml and provide these options into another interface called ChatClient. You may find more configuration options in Spring AI docs. Model: The model ID to use. You can use the supported models and model features Temperature: Controls the randomness of the output. Values can range over [0.0,1.0]TopK: Number of token choices for generating the next token. Conclusion Spring AI is a great tool that helps developers smoothly integrate with different AI models. As of writing this article, Spring AI supports a huge variety of chat models, including but not limited to Open AI, Ollama, and Deepseek AI. I hope you found this article helpful and that it will inspire you to explore Spring AI more deeply.
Recommender systems serve as the backbone of e-commerce, streaming platforms, and online marketplaces, enabling personalized user experiences by predicting preferences and suggesting items based on historical interactions. They are built using explicit and/or implicit feedback from users. Explicit feedback includes direct user inputs, such as ratings and reviews, which provide clear indications of preference but are often sparse. Implicit feedback, such as clicks, views, purchase history, and dwell time, is more abundant but requires specialized algorithms to interpret user intent accurately. In contrast to conventional supervised learning tasks, recommender systems often grapple with implicit feedback, extreme data sparsity, and high-dimensional user-item interactions. These characteristics distinguish them from traditional regression or classification problems. The Netflix Prize competition was a milestone in this field, showcasing the superiority of latent factor models with matrix factorization over heuristic-based or naïve regression approaches. This article examines why standard regression models fall short in recommendation settings and outlines best practices for designing effective collaborative filtering systems. Problem Definition The core of the recommendation problem lies in the user-item matrix, denoted as Y, where Yui represents the rating assigned by user u to item i. In real-world datasets, this matrix is typically sparse, i.e., with a majority of entries missing. For instance, in the Netflix Prize dataset, each movie was rated by approximately 5,000 out of 500,000 users, resulting in a predominantly empty matrix (MIT 2025). This sparsity poses a significant challenge. Furthermore, the prevalence of implicit feedback (e.g., clicks, views) over explicit ratings adds another layer of complexity to generating accurate recommendations. Why Traditional Regression Struggles in Recommender Systems? For instance, in a movie recommendation system, a naive approach would be to treat the task as a regression problem, using features such as movie and user metadata, e.g., genre, actors, director, release year, and user preferences, to predict unknown user ratings. However, this approach has several limitations: Feature selection. Supervised learning depends on well-defined input features. However, in such problems, the determining factors — such as user preferences — are often hidden, difficult to engineer, and challenging to quantify.Sparse data and missing interactions. The user-item matrix in recommendation systems is inherently sparse, with most entries missing. This sparsity makes direct regression on raw ratings impractical.The cold start problem. New users and items often lack sufficient historical data for accurate predictions. For example, a new movie may not have enough ratings to assess its popularity, and new users may not have rated enough items to discern their preferences. Imputing missing ratings is also not a viable solution, as it fails to capture the behavioral context necessary for accurate recommendations. This presents a need for an alternative approach that does not rely solely on predefined item features. Collaborative filtering addresses these limitations by leveraging user-item interactions to learn latent representations, making it one of the most effective techniques in modern recommender systems. Collaborative Filtering Collaborative filtering operates on the principle that users who exhibit similar users are likely to share similar preferences. Unlike supervised regression techniques that rely on manually engineered features, collaborative filtering directly learns patterns from user-item interactions, making it a powerful and scalable approach for personalized recommendations. K-Nearest Neighbors (KNN) KNN, a supervised learning classifier, can be utilized for collaborative filtering. It provides recommendations for a user by looking at feedback from similar users. In this method, given a similarity function, S(u,v), between two users, u and v, a user’s rating for an item can be estimated as a weighted average of the ratings of their nearest neighbors. Common similarity measures include: Cosine similarity. Measures the cosine of the angle between the preference vectors of two users. It is particularly useful when user ratings are sparse and lack an inherent scale.Pearson correlation. Adjusts for differences in individual rating biases, making it more reliable when users have different rating scales. However, the effectiveness of KNN is limited by its dependence on the choice of similarity measure. Matrix Factorization Matrix factorization is a powerful technique for recommendation systems that decomposes the sparse user-item matrix Y into two lower-dimensional matrices, U and V, such that: Y≈UV U represents user-specific latent factors, and V represents item-specific latent factors. These latent factors capture the underlying features determining user preferences and item characteristics, enabling more accurate predictions even in the presence of missing data. Matrix factorization can be implemented with techniques such as singular value decomposition and alternating least squares. Best Practices for Collaborative Filtering Data Preprocessing Data preprocessing steps include handling missing values, removing duplicates, and normalizing data. Scalability As the size of the user-item matrix grows, computational efficiency becomes a concern. Approximate nearest neighbors or alternating least squares are preferred for handling large datasets. Diversity in Recommendation A good recommender system should also prioritize diversity, i.e., recommend a variety of items, including novel or unexpected choices, which can enhance user satisfaction and engagement. Handling Implicit Feedback In many real-world scenarios, explicit user ratings are scarce, and systems must rely on implicit feedback (e.g., clicks, views, or purchase history). Specialized algorithms like Weighted Alternating Least Squares are designed to handle implicit feedback effectively. These methods interpret user behavior as indicators of preference, enabling accurate predictions even without explicit ratings. Addressing the Cold Start Problem Recommendations for new users or items with limited or no interaction data is a challenge that can addressed by: Hybrid Models Combining collaborative filtering with content-based filtering or metadata-based approaches can effectively address the cold start problem. For example, if a new item lacks sufficient ratings, the system can use its metadata, e.g., genre, actors, or product descriptions, to recommend it based on similarity to other items. Similarly, for new users, demographic information or initial preferences can be used to bootstrap recommendations. Transfer Learning Transfer learning is a powerful technique for leveraging knowledge from related domains or user groups to improve recommendations for new users or items. For instance, in industries like healthcare or e-commerce, where user-item interactions may be sparse, transfer learning can apply insights from a data-rich domain to enhance predictions in a data-scarce one. Active Learning Active learning techniques can help gather targeted feedback from new users or for new items. By strategically prompting users to rate or interact with specific items, the system can quickly build a profile and improve recommendations. This approach is suited for scenarios where user engagement is high but initial data is sparse. Default Recommendations For new users or items, default recommendations based on popular or trending items can serve as a temporary solution until sufficient data is collected. While not personalized, this approach ensures that users receive relevant content while the system learns their preferences over time. Collaborative filtering is a powerful tool for building recommendation systems. By following best practices of proper data preprocessing, regularization, and evaluation and leveraging advanced techniques like hybrid models and transfer learning, practitioners can create robust and scalable recommender systems that deliver accurate, diverse, and engaging recommendations. References Massachusetts Institute of Technology (MIT). (n.d.) MITx 6.86x: Machine Learning with Python - From Linear Models to Deep Learning [Accessed 23 Feb. 2025].
Introduction Note: You can download the source from GitHub. I’ve been working on multi-tenant applications in the .NET ecosystem for quite a while, and one of the toughest challenges I consistently run into is secure tenant isolation — especially when different customers expect separate user bases, roles, and management capabilities. My favorite solution to this has been Keycloak, an open-source identity and access management tool. With Keycloak, I rely on realms to isolate tenant configurations, then tie everything neatly together in my .NET application. Here’s how I do it. Why Use Keycloak Realms? Keycloak realms let you split each tenant into its own “space,” complete with distinct users, groups, roles, and OAuth/OpenID Connect clients. Since every realm operates like a silo, I can onboard a new customer (tenant) just by spinning up a new realm — no major code changes required. It’s a lot simpler than forcing one giant user directory to handle everyone in one place. How It Fits Into .NET From an ASP.NET Core standpoint, realms simplify how I structure authentication flows. Each realm is assigned its own issuer (Authority), ClientID, and optional Client Secret. All I need to do in my .NET code is determine which tenant the request belongs to, then configure the authentication pipeline to point to the right realm. Keycloak Setup: Realms and Clients Let’s imagine I have two tenants: Tenant A and Tenant B. In Keycloak: Create realm “tenantA.” Within it, add clients such as admin_portal or distributor_portal.Create realm “tenantB.” Similar approach: create a client for each distinct application. Each realm keeps track of its own user directory. If Tenant A’s admin wants to disable a user, that’s done in the Tenant A realm only, leaving Tenant B untouched. Creating isolated realm Isolated client for different products Why So Convenient? Because once realms and clients are configured, the day-to-day tasks for each tenant are neatly partitioned. Tenant A’s staff can’t mess with Tenant B’s configuration and vice versa. Wiring It Up in .NET Detecting the Tenant In most of my projects, I rely on subdomains or frontend settings to figure out which tenant is currently hitting the application — like tenantA.myapp.com versus tenantB.myapp.com. If subdomains aren’t an option, you can pass a custom header, parse a path segment, or store a tenant identifier in JWT claims. C# var host = context.Request.Host.Host; var tenant = host.Contains("tenantA") ? "tenantA" : "tenantB"; Storing Realm Configurations I keep a simple dictionary or JSON file that maps tenant names to Keycloak settings: C# public static class MultiTenantAuthOptions { public static Dictionary<string, TenantConfig> TenantSettings = new() { ["tenantA"] = new TenantConfig { RealmName = "tenantA", AuthorityUrl = "http://localhost:8080/realms/tenantA", ClientId = "admin_portal", ClientSecret = "SECRET_A" }, ["tenantB"] = new TenantConfig { RealmName = "tenantB", AuthorityUrl = "http://localhost:8080/realms/tenantB", ClientId = "dist_portal", ClientSecret = "SECRET_B" } }; } This way, my application picks the right AuthorityUrl and ClientId at runtime. Custom JWT Bearer Events ASP.NET Core normally uses a static configuration for JWT Bearer. But with multi-tenancy, I override it on the fly. For example: C# public class MultiTenantJwtBearerEvents : JwtBearerEvents { public override Task MessageReceived(MessageReceivedContext context) { var host = context.Request.Host.Host; var tenantKey = host.Contains("tenantA") ? "tenantA" : "tenantB"; if (MultiTenantAuthOptions.TenantSettings.TryGetValue(tenantKey, out var cfg)) { context.Options.Authority = cfg.AuthorityUrl; context.Options.TokenValidationParameters.ValidAudience = cfg.ClientId; } return base.MessageReceived(context); } } After that, my code can handle logins from different realms without any friction. Realm-Specific Admins Setting Up Admin Roles Keycloak allows you to give each realm its own admin user. I like this because it gives each tenant control over their own user base, roles, and security rules — without risking cross-tenant confusion. The steps are straightforward: Go to Manage > Users, create a user named, say, realmAdminA.Assign roles like manage-users, view-realm, and so on, specifically under the realm-management client in that realm.That user can now do admin tasks only inside their realm. Typical admin-level actions include: Disabling/enabling usersResetting passwordsHandling group membershipChecking user sessions and logging users outConfiguring 2FA requirements Because these admin permissions are scoped to a single realm, one tenant’s admin can’t see or break anything that belongs to another tenant. That separation is precisely what multi-tenant architectures need. Logging and User Sessions Keycloak logs all sorts of events — logins, logouts, failed attempts, password resets, etc. This is great for compliance and troubleshooting. Each realm admin can view logs relevant only to that realm. Users, on their side, can see their own active sessions and end them if needed. It’s straightforward and keeps responsibility clearly divided. Configure what listeners receive events for the realm. Configure what user events need to log for the realm. Configure what admin events need to log for the realm. Events are records of user and admin events in this realm. Sessions are sessions of users in this realm and the clients that they access within the session. Migrating Existing Users If you’re already running a legacy user store, you probably want to bring that data into Keycloak. I usually consider two methods: Just-in-time (JIT) Migration When a user logs in for the first time, your code checks the old database, validates credentials, and then creates the user in Keycloak.Bulk Migration Write a script that pulls all user records from the old system and calls Keycloak’s Admin REST API to create those users. If you have hashed passwords in a non-standard format, you might require a forced password reset upon first Keycloak login. That’s typically safer than storing plaintext or messing around with custom hashing. Data Isolation in .NET Even though Keycloak realms takes care of identity isolation, your .NET application still has to respect multi-tenancy at the data layer. Usually, I rely on: Separate DB schemas or separate connection strings per tenantScoped services that inject the current tenant ID and automatically filter queriesPartitioned caches so you don’t accidentally serve cached data from Tenant A to Tenant B It’s a two-layered approach: Keycloak ensures no identity overlap, and your application ensures no data overlap. Final Thoughts and Best Practices In my experience, setting up a realm per tenant simplifies nearly every security-related issue. It’s easy to reason about, easy to hand over partial admin rights, and straightforward to spin up new realms as you grow. A few parting tips: Automate realm creation: Take advantage of Keycloak’s Admin REST API or its realm import/export features.Monitor carefully: Use Keycloak’s built-in metrics or external tools (Prometheus, Grafana) to keep tabs on performance, login activity, and potential brute-force attempts.Watch the realm count: If you expect hundreds or thousands of tenants, weigh the overhead carefully — sometimes, grouping smaller tenants in a single realm is necessary. By combining these Keycloak realms strategies with tenant-aware .NET code, I’ve built SaaS platforms that scale neatly while keeping each customer’s user base locked down. If you’re looking to simplify multi-tenant identity management on .NET, Keycloak realms might just save you a ton of hassle. References Keycloak DocumentationASP.NET Core AuthenticationOpenID Connect Specs
I think we need to be realistic when we talk about AI's role in software development. It's not "hit a button and generate code." For me, it's best positioned to maximize efficiency. It's not just a tool for getting rid of developers. Whenever I start a new project, I often get stuck on "blank page syndrome." Half of the battle in software development is getting 90% of the knowledge I need in order to start writing code — which is a big part of engineering solutions. Previously, researching libraries, understanding code, learning what the best practices are, and gotchas of the thing I'm about to do could take days to figure out. You'd go to the library, maybe look at the docs, read the docs, and even then, you don't know which ones you need to read. So then you maybe play with it for a bit, figure out what you don't know, go back to the docs, maybe you run into a problem that isn't clear there. It’s convoluted, frustrating, and very trial-and-error. AI removes this guesswork and abstract-ivity. With iteration speeds hitting ridiculously high multipliers in the development process, it is now much easier to A/B (or even A/B/C/D) test. You can try a few implementations in parallel and see how your users or customers respond to them. For small teams that don't have the resources to be experimenting like that pre-AI, this has completely changed our approach. It supports what Sam Altman recently tweeted — when GPT4 first came out, it cost more, and now it's much cheaper, but usage is actually much higher. They make much more money now even though it's cheaper. A New Level of Trust The thing about speed in AI development is it all comes down to trust, right? Like, you can only move as fast as you're willing to trust the code that's being produced. I like to compare this to the self-driving car paradox where people will happily get in a car with a human even though the human has a, say, 1 in 1,000 chance of making a mistake. Compare that to a self-driving car that might have a 1 in 10,000 chance of making a mistake, and many people will still go with the human. The problem is with accountability more than anything. On the topic of accountability, for AI-generated code, my feeling is that you'd need something like 99% accuracy before you could just approve it without looking. That's a much higher bar than we set for human developers, but it makes sense. If you're generating billions of lines of code and you've now redirected all your human efforts to reviewing that, that doesn't seem like a true efficiency gain. The bottleneck always comes back, I think, to context. AI can write code that looks perfect on the surface, but that doesn't always mean it's right. Both humans and AI make mistakes but in different ways. When a human developer has a particular blind spot or habit, you'll see that same pattern show up in their PRs — it's predictable and specific to that developer. But with AI, the issues come from fundamental limitations in how LLMs understand context and codebases. That's a much broader, systematic problem. Reshaping the Developer Journey "Developers" might not even be developers anymore. Daily, I see people on various social media channels who aren't even traditionally technical jumping into development. They might not be junior developers in the classic sense, but they're tech-adjacent people with ideas who can fire up Claude or ChatGPT, get a basic app running, and suddenly, they're making thousands in monthly recurring revenue. That just wasn't possible before. One thing about junior engineers is they're famous for diving straight into coding. And, with the safety net of AI to proofread everything and hold your hand, that impulse often remains unchecked. They completely overlook the thinking phase before writing any code. It’s convenient because juniors often don't even know what questions they should be asking. Now, with AI, you can do what we call "rubber duck debugging," where you talk through your problem out loud like you're explaining it to a rubber duck. Except instead of a duck, you've got an AI that can actually talk back and help you understand the problem better. This new path from junior to senior developer isn't just about getting better at writing code. The real skill that takes you to the next level is being able to take a big, messy problem, whether it's technical or coming from the business side, and break it down into actionable pieces. That's what engineering is really about. When you look at top-level engineers, they might barely write any code at all. Their value comes from breaking down complex problems. That's the true meaning of being an engineer. The Road Ahead I get the pushback from experienced developers — just check out the ExperiencedDevs subreddit, and you'll see how negative the reaction gets whenever AI comes up. It's understandable. But this is no different from any other industry where veterans resist new approaches. There's always this mix of valid concerns and just resistance to change, especially around what skills are considered "the basics." The thing is, as the industry evolves, what we consider fundamental keeps changing, too. I'm not saying AI-generated code is better than human code yet. But that shift is coming, especially with the recent breakthroughs in reinforcement learning. We're moving beyond just mimicking human developers — now we're heading toward something potentially superhuman. Instead of showing AI exactly what to do, you give it a goal and a way to measure its progress. Then, you let it experiment and figure things out on its own. Through this process, it discovers solutions that might never occur to a human developer. We're not heading toward fewer developers — we're heading toward different types of development work.
Welcome back to the “Text to Action” series, where we build intelligent systems that transform natural language into real-world actionable outcomes using AI. In Part 1, we established our foundation by creating an Express.js backend that connects to Google Calendar’s API. This gave us the ability to programmatically create calendar events through an exposed API endpoint. Today, we’re adding the AI magic — enabling users to simply type natural language descriptions like “Schedule a team meeting tomorrow at 3 pm” and have our system intelligently transform such words into adding an actual calendar event action. What We’re Building We’re bridging the gap between natural human language and structured machine data. This ability to parse and transform natural language is at the heart of modern AI assistants and automation tools. The end goal remains the same: create a system where users can type or say “create a party event at 5 pm on March 20” and instantly see it appear in their Google Calendar. This tutorial will show you how to: Set up a local language model using OllamaDesign an effective prompt for text-to-json conversionBuild a natural language API endpointCreate a user-friendly interface for text inputHandle date, time, and timezone complexities The complete code is available on GitHub. Prerequisites Before starting, please make sure you have: Completed Part 1 setupNode.js and npm installedOllama installed locallyThe llama3.2:latest model pulled via Ollama Plain Text # Install Ollama from https://ollama.com/ # Then pull the model: ollama pull llama3.2:latest Architecture Overview Here’s how our complete system works: User enters natural language text through the UI or API call.System sends text to Ollama with a carefully designed prompt.Ollama extracts structured data (event details, time, date, etc.).System passes the structured data to Google Calendar API.Google Calendar creates the event.User receives confirmation with event details. The magic happens in the middle steps, where we convert unstructured text to structured data that APIs can understand. Step 1: Creating the NLP Service First, let’s install the axios package for making HTTP requests to our local Ollama instance: Plain Text npm install axios Now, create a new file nlpService.js to handle the natural language processing. Here are the key parts (full code available on GitHub): JavaScript const axios = require('axios'); // Main function to convert text to calendar event const textToCalendarEvent = async (text, timezone) => { try { const ollamaEndpoint = process.env.OLLAMA_ENDPOINT || 'http://localhost:11434/api/generate'; const ollamaModel = process.env.OLLAMA_MODEL || 'llama3.2:latest'; const { data } = await axios.post(ollamaEndpoint, { model: ollamaModel, prompt: buildPrompt(text, timezone), stream: false }); return parseResponse(data.response); } catch (error) { console.error('Error calling Ollama:', error.message); throw new Error('Failed to convert text to calendar event'); } }; // The core prompt engineering part const buildPrompt = (text, timezone) => { // Get current date in user's timezone const today = new Date(); const formattedDate = today.toISOString().split('T')[0]; // YYYY-MM-DD // Calculate tomorrow's date const tomorrow = new Date(today.getTime() + 24*60*60*1000); const tomorrowFormatted = tomorrow.toISOString().split('T')[0]; // Get timezone information const tzString = timezone || Intl.DateTimeFormat().resolvedOptions().timeZone; return ` You are a system that converts natural language text into JSON for calendar events. TODAY'S DATE IS: ${formattedDate} USER'S TIMEZONE IS: ${tzString} Given a text description of an event, extract the event information and return ONLY a valid JSON object with these fields: - summary: The event title - description: A brief description of the event - startDateTime: ISO 8601 formatted start time - endDateTime: ISO 8601 formatted end time Rules: - TODAY'S DATE IS ${formattedDate} - all relative dates must be calculated from this date - Use the user's timezone for all datetime calculations - "Tomorrow" means ${tomorrowFormatted} - For dates without specified times, assume 9:00 AM - If duration is not specified, assume 1 hour for meetings/calls and 2 hours for other events - Include timezone information in the ISO timestamp Examples: Input: "Schedule a team meeting tomorrow at 2pm for 45 minutes" Output: {"summary":"Team Meeting","description":"Team Meeting","startDateTime":"${tomorrowFormatted}T14:00:00${getTimezoneOffset(tzString)}","endDateTime":"${tomorrowFormatted}T14:45:00${getTimezoneOffset(tzString)}"} Now convert the following text to a calendar event JSON: "${text}" REMEMBER: RESPOND WITH RAW JSON ONLY. NO ADDITIONAL TEXT OR FORMATTING. `; }; // Helper functions for timezone handling and response parsing // ... (See GitHub repository for full implementation) module.exports = { textToCalendarEvent }; The key innovation here is our prompt design that: Anchors the model to today’s dateProvides timezone awarenessGives clear rules for handling ambiguous casesShows examples of desired output format Step 2: Calendar Utility Function Utility module for calendar operations. Here’s the simplified version (utils/calendarUtils.js): JavaScript const { google } = require('googleapis'); // Function to create a calendar event using the Google Calendar API const createCalendarEvent = async ({ auth, calendarId = 'primary', summary, description, startDateTime, endDateTime }) => { const calendar = google.calendar({ version: 'v3', auth }); const { data } = await calendar.events.insert({ calendarId, resource: { summary, description: description || summary, start: { dateTime: startDateTime }, end: { dateTime: endDateTime } } }); return { success: true, eventId: data.id, eventLink: data.htmlLink }; }; module.exports = { createCalendarEvent }; Step 3: Updating the Express App Now, let’s update our app.js file to include the new natural language endpoint: JavaScript // Import the new modules const { textToCalendarEvent } = require('./nlpService'); const { createCalendarEvent } = require('./utils/calendarUtils'); // Add this new endpoint after the existing /api/create-event endpoint app.post('/api/text-to-event', async (req, res) => { try { const { text } = req.body; if (!text) { return res.status(400).json({ error: 'Missing required field: text' }); } // Get user timezone from request headers or default to system timezone const timezone = req.get('X-Timezone') || Intl.DateTimeFormat().resolvedOptions().timeZone; // Convert the text to a structured event with timezone awareness const eventData = await textToCalendarEvent(text, timezone); const { summary, description = summary, startDateTime, endDateTime } = eventData; // Create the calendar event using the extracted data const result = await createCalendarEvent({ auth: oauth2Client, summary, description, startDateTime, endDateTime }); // Add the parsed data for reference res.status(201).json({ ...result, eventData }); } catch (error) { console.error('Error creating event from text:', error); res.status(error.code || 500).json({ error: error.message || 'Failed to create event from text' }); } }); Step 4: Building the User Interface We’ll create a dedicated HTML page for natural language input (public/text-to-event.html). Here's a simplified version showing the main components: HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Text to Calendar Event</title> <!-- CSS styles omitted for brevity --> </head> <body> <div class="nav"> <a href="/">Standard Event Form</a> <a href="/text-to-event.html">Natural Language Events</a> </div> <h1>Text to Calendar Event</h1> <p>Simply describe your event in natural language, and we'll create it in your Google Calendar.</p> <div class="container"> <h2>Step 1: Authenticate with Google</h2> <a href="/auth/google"><button class="auth-btn">Connect to Google Calendar</button></a> </div> <div class="container"> <h2>Step 2: Describe Your Event</h2> <div> <div>Try these examples:</div> <div class="example">Schedule a team meeting tomorrow at 2pm for 45 minutes</div> <div class="example">Create a dentist appointment on April 15 from 10am to 11:30am</div> <div class="example">Set up a lunch with Sarah next Friday at noon</div> </div> <form id="event-form"> <label for="text-input">Event Description</label> <textarea id="text-input" required placeholder="Schedule a team meeting tomorrow at 2pm for 45 minutes"></textarea> <button type="submit">Create Event</button> <div class="loading" id="loading">Processing your request <span></span></div> </form> <div id="result"></div> </div> <script> // JavaScript code to handle the form submission and examples // See GitHub repository for full implementation </script> </body> </html> The interface provides clickable examples and a text input area and displays results with a loading indicator. Step 5: Creating a Testing Script For easy command-line testing to automatically detect your current timezone, here’s a simplified version of our shell script test-text-to-event.sh: Shell #!/bin/bash # Test the text-to-event endpoint with a natural language input # Usage: ./test-text-to-event.sh "Schedule a meeting tomorrow at 3pm for 1 hour" TEXT_INPUT=${1:-"Schedule a team meeting tomorrow at 2pm for 45 minutes"} # Try to detect system timezone TIMEZONE=$(timedatectl show --property=Timezone 2>/dev/null | cut -d= -f2) # Fallback to a popular timezone if detection fails TIMEZONE=${TIMEZONE:-"America/Chicago"} echo "Sending text input: \"$TEXT_INPUT\"" echo "Using timezone: $TIMEZONE" echo "" curl -X POST http://localhost:3000/api/text-to-event \ -H "Content-Type: application/json" \ -H "X-Timezone: $TIMEZONE" \ -d "{\"text\": \"$TEXT_INPUT\"}" | json_pp echo "" Don’t forget to make it executable: chmod +x test-text-to-event.sh If you know your timezone already, you could pass it directly like below Shell curl -X POST http://localhost:3000/api/text-to-event \ -H "Content-Type: application/json" \ -H "X-Timezone: America/New_York" \ -d '{"text": "Schedule a team meeting tomorrow at 3pm for 1 hour"}' Step 6: Updating Environment Variables Create or update your .env file to include the Ollama settings: Plain Text # Google Calendar API settings (from Part 1) GOOGLE_CLIENT_ID=your_client_id_here GOOGLE_CLIENT_SECRET=your_client_secret_here GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback PORT=3000 # Ollama LLM settings OLLAMA_ENDPOINT=http://localhost:11434/api/generate OLLAMA_MODEL=llama3.2:latest # Server configuration PORT=3000 The Magic of Prompt Engineering The heart of our system lies in the carefully designed prompt that we send to the language model. Let’s break down why this prompt works so well: Context setting. We tell the model exactly what we want it to do — convert text to a specific JSON format.Date anchoring. By providing today’s date, we ground all relative date references.Timezone awareness. We explicitly tell the model what timezone to use.Specific format. We clearly define the exact JSON structure we expect back.Rules for ambiguities. We give explicit instructions for handling edge cases.Examples. We show the model exactly what good outputs look like. This structured approach to prompt engineering is what makes our text conversion reliable and accurate. Testing the Complete System Now that everything is set up, let’s test our system: Start the server: npm run startMake sure Ollama is running with the llama3.2:latest modelOpen your browser to http://localhost:3000/text-to-event.htmlAuthenticate with Google (if you haven’t already)Enter a natural language description like “Schedule a team meeting tomorrow at 2 pm”Click “Create Event” and watch as it appears in your calendar! You can also test from the command line: Plain Text ./test-text-to-event.sh "Set up a project review on Friday at 11am" To test if your Ollama is running as expected, try this test query: Shell curl -X POST http://localhost:11434/api/generate \ -H "Content-Type: application/json" \ -d '{ "model": "llama3.2:latest", "prompt": "What is the capital of France?", "stream": false }' The Complete Pipeline Let’s review what happens when a user enters text like “Schedule a team meeting tomorrow at 2 pm for 45 minutes”: The text is sent to our /api/text-to-event endpoint, along with the user's timezoneOur NLP service constructs a prompt that includes: Today’s date (for reference)The user’s timezoneClear instructions and examplesThe user’s textOllama processes this prompt and extracts the structured event data: JSON { "summary": "Team Meeting", "description": "Team Meeting", "startDateTime": "2025-03-09T14:00:00-05:00", "endDateTime": "2025-03-09T14:45:00-05:00" } Our app passes this structured data to the Google Calendar APIThe Calendar API creates the event and returns a success message with a linkWe display the confirmation to the user with all the details This demonstrates the core concept of our “Text to Action” series: transforming natural language into structured data that can trigger real-world actions. Conclusion and Next Steps In this tutorial, we’ve built a powerful natural language interface for calendar event creation. We’ve seen how: A well-crafted prompt can extract structured data from free-form textTimezone and date handling requires careful considerationModern LLMs like llama3.2 can understand and process natural language reliably But we’ve only scratched the surface of what’s possible. In future episodes of the “Text to Action” series, we’ll explore: Adding voice recognition for hands-free event creationBuilding agent-based decision-making for more complex tasksConnecting to multiple services beyond just calendars The complete code for this project is available on GitHub. Stay tuned for Part 3, where we’ll add voice recognition to our system and make it even more accessible! Resources GitHub RepositoryPart 1: Calendar API FoundationOllama DocumentationGoogle Calendar API Let me know in the comments what you’d like to see in the next episode of this series!
In Playwright, you can run tests using headed and headless modes. In the earlier versions of Playwright, running tests in headed mode was somewhat challenging. To improve the headed mode experience, Playwright introduced UI mode in version 1.32. Playwright UI mode provides a visual interface for running and debugging tests. It allows you to inspect elements, step through tests interactively, and get real-time feedback, making troubleshooting more intuitive. In this blog, we look at how to use Playwright UI mode to run and debug tests. What Is Playwright UI Mode? Playwright UI mode is an interactive feature that allows you to run, inspect, and debug tests in a visually intuitive environment. With UI mode, users can see exactly what’s happening on their website during test execution, making it easier to identify issues and improve test reliability. This interface brings a visual and user-friendly approach to testing, which contrasts with the traditionally code-centric methods of automation testing. Key Benefits Enables you to view the test run in real time. When each step is executed, the associated actions are presented side by side to demonstrate how the elements are used. Such live feedback may be useful to determine if the test script performs correctly when interacting with the elements and if there are any deviations.Allows you to stop test execution at any stage and check the page elements for issues such as timing or selector issues.Comes with a trace viewer that provides details about every test run. The trace viewer records all the actions performed while a test is in progress, using screenshots and network traffic. Makes it easy to find reliable selectors for page elements. When you hover over various elements, Playwright shows the most stable selectors. This capability is crucial in creating resilient tests, as fragile tests may fail due to wrong or weak selectors caused by UI deviations.Lets you easily execute test steps and see the results if you change them. This streamlines the testing process by reducing the number of full test cycles needed. It helps you save time, especially when working on large-scale projects, where tests can take time to execute completely.Supports automated screenshots and video recordings of test runs. These visual aids clearly represent the testing journey step by step, helping teams understand exactly where issues occur. Components of Playwright UI Mode The screenshot shows the Playwright UI mode, which provides a graphical interface for running and debugging Playwright tests. Let's break down each component in detail: Test Explorer (Left Panel) The test explorer comes with the following features: Filters tests (through search box) by keywords, tags (e.g., @tau), or metadata, helping to narrow down tests in large suites.Displays tests of all statuses (passed, failed, skipped) by default, with the option to filter specific states.Filters tests based on the selected browser (e.g., Chromium, Firefox, WebKit).The above screenshot shows the following test files: api.spec.jsexample.spec.jsuiLT.spec.jsvisual.spec.js You can also click on test files to execute or debug them. Timeline (Top Section): It displays a graph tracking test execution over time. This helps visualize the duration of each test or step. It is also useful for identifying performance bottlenecks and debugging slow-running tests. Action Explorer (Center Panel) It records and displays test execution details step by step. The Action Explorer includes multiple tabs: Actions: Logs user interactions (clicks, inputs, navigation, etc.).Metadata: Displays additional test attributes (e.g., environment, priority).Action: Lists recorded actions performed during the test.Before/After: Shows test setup and teardown steps.Pick Locator: Helps identify element locators for test scripts. Browser Preview (Right Panel) It provides a real-time view of test execution. In the screenshot, the browser shows that the KaneAI link is opened. Developer Tools (Bottom Panel) It contains tabs similar to browser dev tools: Locator: Assists in finding elements.Source: Displays the test script source code.Call: Logs API calls, function executions, or internal Playwright actions.Log: Provides a detailed execution log.Errors: Lists runtime errors (e.g., element not found, network failures).Console: Displays logs and debug messages.Network: Logs network requests made during test execution.Attachments: Stores test artifacts like screenshots, videos, and downloaded files.Annotations: Highlights test case statuses (e.g., skipped, fixme, slow). How to Use Playwright UI Mode? Now, let’s look at how to set up UI mode in Playwright: Install Playwright v1.32 by running the below command: JavaScript npm install @playwright/test@1.32 In case you want to install the latest version of Playwright, run the below command: JavaScript npm init playwright@latest Open the Playwright in UI mode by running the below command: JavaScript npx playwright test --ui It will open the Playwright Test Runner in your browser, where you can interact with your tests and gain insights into each step of the test execution process. This command is particularly helpful during development when you need a more visual, hands-on approach to test automation. The --ui flag enables UI mode, launching an interactive interface in your browser. After installation, the folder structure looks like as shown below: Consider the test script below. It navigates to the KaneAI page, verifies the title, and clicks a button inside a specific section. Then, it fills the First Name and Last Name fields with "KaneAI" and "Automation," respectively. It ensures the page loads correctly and inputs test data. JavaScript // @ts-check const { test, expect } = require("@playwright/test"); test("Open the site , verify the title, and fill data in the fields", async ({ page, }) => { await page.goto("https://www.lambdatest.com/kane-ai"); await expect(page).toHaveTitle(/Kane AI - World's first AI E2E Testing Agent/); await page.locator('section').filter({ hasText: 'KaneAI - Testing' }).getByRole('button').click(); await page.getByRole('textbox', { name: 'First Name*' }).fill('KaneAI') await page.getByRole('textbox', { name: 'Last Name*' }).fill('Automation') }); To run the above test script, use the command: JavaScript npx playwright test --ui Output Features of Playwright UI Mode Now let’s look at different UI mode features offered by Playwright and how you can use them: Filtering Tests You can filter the executed test script by text or @tag. Also, With the status ‘passed,’ it will display only the passed test cases. Status with ‘failed’ will display all failed test cases, and the status ‘skipped’ will display if there are any skipped test cases during the execution. Additionally, you can filter by the project (Chromium, Firefox, and WebKit) and display test cases that are executed in respective browsers. Pick Locator The Pick Locator feature in Playwright UI mode assists in identifying locators for elements on a webpage. To capture the locator of a specific field, simply click on the circle icon (as shown in the screenshot) and hover over the desired field. This will highlight and display the corresponding locator for easy selection. Actions and Metadata In the Actions tab, you can see all the actions we have performed against a particular test. When you hover over each command, you can see the change in DOM on the right side. In the Metadata tab, you can see the details about the particular test case start time, browser, viewport, and count, which include pages, actions, and events. Timeline View At the top of the trace, you can see a timeline view of each action of your test. The timeline shows image snapshots after hovering over it, and double-clicking any action will show its time range. The slider adjustment lets you choose additional actions that directly filter the Actions tab while controlling log visibility in the console and network tabs. Watch Feature The Watch feature enhances the testing experience by automatically rerunning the test when changes are made to the test file. This eliminates the need for manually re-executing tests after modifications, making the development and debugging process more efficient. To enable the Watch in Playwright UI mode, simply click on the watch icon next to the specific test case. Once enabled, any changes made to that test file will automatically trigger the test to re-execute. Source, Call, and Log Source In the Source tab, you can see the source code of the selected test case. Call In the Call tab, you can view the details of a particular command, including its start time, the time it takes to complete and other parameters. Log The Log tab provides the execution of the particular command in detail. In the screenshot below, you can see the command is attempting to fill the "Last Name" field with the value "Automation." It first waits for the textbox with the role textbox and the name Last Name* to be located. Once the locator resolves to the correct <input> element, the script attempts to perform the fill action. However, before filling, it ensures that the element is visible, enabled, and editable. Errors The Error tab lets you know the reason for test failure. In case any test fails, the Timeline at the top is also highlighted with a red color where the error comes. Console The Console tab shows the log from the test and browser. The icons below indicate whether the console log came from the browser or the test file. Network In the Network tab, you can see all the requests made during the test execution. You can filter the network requests and sort them by different types of requests, methods, status codes, content types, sizes, and durations. Attachments The Attachments tab helps with visual image comparison by identifying the difference between the images. By clicking on Diff, you can see the difference between the actual and expected images. Annotations Annotations are special markers that you can add to test suites to change the behavior of the test execution. Any of the tests are marked as test.skip(), test.fail(), test.fixme(), test.slow() will show in the UI mode. Overall, Playwright UI mode makes debugging tests easier with its interactive interface, but running tests locally has limitations. Wrapping Up With Playwright UI mode, you can fully access automated test functionality without any hindrances. The real-time visual testing framework enhances debugging, improves test reliability, and accelerates test development. Integrated features such as live test execution monitoring, step-by-step debugging, trace viewer, and selector inspection make the test automation process more efficient. Leveraging Playwright UI mode can optimize test automation workflows, leading to higher test quality and reduced debugging time. This streamlined approach enables you to create more effective test scripts and quickly resolve test failures.
DZone events bring together industry leaders, innovators, and peers to explore the latest trends, share insights, and tackle industry challenges. From Virtual Roundtables to Fireside Chats, our events cover a wide range of topics, each tailored to provide you, our DZone audience, with practical knowledge, meaningful discussions, and support for your professional growth. DZone Events Happening Soon Below, you’ll find upcoming events that you won't want to miss. Unpacking the 2025 Developer Experience Trends Report: Insights, Gaps, and Putting it into Action Date: March 19, 2025Time: 1:00 PM ET Register for Free! We’ve just seen the 2025 Developer Experience Trends Report from DZone, and while it shines a light on important themes like platform engineering, developer advocacy, and productivity metrics, there are some key gaps that deserve attention. Join Cortex Co-founders Anish Dhar and Ganesh Datta for a special webinar, hosted in partnership with DZone, where they’ll dive into what the report gets right—and challenge the assumptions shaping the DevEx conversation. Their take? Developer experience is grounded in clear ownership. Without ownership clarity, teams face accountability challenges, cognitive overload, and inconsistent standards, ultimately hampering productivity. Don’t miss this deep dive into the trends shaping your team’s future. Accelerating Software Delivery: Unifying Application and Database Changes in Modern CI/CD Date: March 25, 2025Time: 1:00 PM ET Register for Free! Want to speed up your software delivery? It’s time to unify your application and database changes. Join us for Accelerating Software Delivery: Unifying Application and Database Changes in Modern CI/CD, where we’ll teach you how to seamlessly integrate database updates into your CI/CD pipeline. Petabyte Scale, Gigabyte Costs: Mezmo’s ElasticSearch to Quickwit Evolution Date: March 27, 2025Time: 1:00 PM ET Register for Free! For Mezmo, scaling their infrastructure meant facing significant challenges with ElasticSearch. That's when they made the decision to transition to Quickwit, an open-source, cloud-native search engine designed to handle large-scale data efficiently. This is a must-attend session for anyone looking for insights on improving search platform scalability and managing data growth. Best Practices for Building Secure Data Pipelines with Apache Airflow® Date: April 15, 2025Time: 1:00 PM ET Register for Free! Security is a critical but often overlooked aspect of data pipelines. Effective security controls help teams protect sensitive data, meet compliance requirements with confidence, and ensure smooth, secure operations. Managing credentials, enforcing access controls, and ensuring data integrity across systems can become overwhelming—especially while trying to keep Airflow environments up–to-date and operations running smoothly. Whether you're working to improve access management, protect sensitive data, or build more resilient pipelines, this webinar will provide the knowledge and best practices to enhance security in Apache Airflow. Generative AI: The Democratization of Intelligent Systemsive Date: April 16, 2025Time: 1:00 PM ET Register for Free! Join DZone, alongside industry experts from Cisco and Vertesia, for an exclusive virtual roundtable exploring the latest trends in GenAI. This discussion will dive into key insights from DZone's 2025 Generative AI Trend Report, focusing on advancements in GenAI models and algorithms, their impact on code generation, and the evolving role of AI in software development. We’ll examine AI adoption maturity, intelligent search capabilities, and how organizations can optimize their AI strategies for 2025 and beyond. What's Next? DZone has more in store! Stay tuned for announcements about upcoming Webinars, Virtual Roundtables, Fireside Chats, and other developer-focused events. Whether you’re looking to sharpen your skills, explore new tools, or connect with industry leaders, there’s always something exciting on the horizon. Don’t miss out — save this article and check back often for updates!
There is no doubt that Python is one of the most popular programming languages. Furthermore, it offers frameworks like Django and Flask. But Django is the most famous among Python developers as it helps them with a rapid development process and pragmatic design. For example, the Object Relational Mapping tool (ORM), routing, and templating features make things easier for developers. But despite these powerful features, there are many mistakes, such as bad application structure and incorrect resource placement, as well as writing fat views and skinny models, etc. These types of problems are not just faced by newbie Python developers, but they are also difficult ones for experienced Python developers as well. In this article, I have listed the most common problems developers face and how to avoid them. 1. Developers Often Use Python’s Global Environment for Project Dependencies This mistake is often made by new Python developers who don’t know about the environment isolation features of Python. You must not use the global environment for project dependencies as it generates dependency issues. Moreover, Python will be unable to use various package versions simultaneously. Further, this will be a significant issue, as various other projects will need different, irreconcilable varieties of the same package. You can get rid of this problem by isolating Python’s environment: Use Virtual Environment You can use a module named virtualenv since it is a tool that helps you build virtual environments in Python. These environments will be separated from those system environments. Using virtualenv will create a folder that will include all the important executable files that your Python project will require to use the packages. Virtualenvwrapper Virtualenvwrapper is a Python package that installs globally. When it comes to creating, deleting, and activating virtual environments, a Python package has a toolset for it. You can keep all the virtual environments in a single folder. Virtual Machines (VM) This is one of the best ways to isolate your environment since an entire virtual machine is dedicated to your application. You can choose from a collection of tools that include VirtualBox, Parallels, and Proxmox. Furthermore, if you integrate it with a VM automation tool named Vagrant, it will deliver results beyond your expectations. Containers For container automation, you can use a Docker tool since it has a lot of third-party tools. Moreover, this tool includes a catching feature that helps to rebuild your containers at a rapid speed. Further, this is very easy to implement, and when you have an idea of how Docker works, then you will find a lot of helpful images like Postgres, MongoDB, Redis, PySpark, etc. So, these are the best ways you can use to master project dependency isolation as well as management. 2: Developers Do Not Pin Project Dependencies in a requirements.txt File Starting your Python project, you should start it with an isolated environment with a requirement.txt file. And when developers install packages via pip/easy_install, they must also add them to their requirement.txt file. Therefore, it will be way easier for you if you deploy a project on the server. There are various modules, parameters, and functions for different versions of packages. Even a small change in the dependency is capable of breaking your package. Hence, it is very important that you pin the particular version of your dependencies in your requirements.txt file. Moreover, there is a list of pip-tools available in Python, and with command-line tools, you will be able to manage those dependencies with ease. This tool is useful since it automatically produces a requirement.txt file that helps to pin all of those dependencies and even a complete dependency tree. Also, keep in mind to have a copy of your dependencies file in the file system, S3 folder, FTP, as well as SFTP. 3: You Don’t Know the Advantages of Both Function-Based Views and Class-Based Views If you use function-based views, then it is a traditional approach to implementing views. Being implemented as normal Python functions, these take an HTTP request as if it is an argument and, therefore, return an HTTP response. Function-Based Views (FBVs) Below, you can read the benefits of using function-based views (FBVs): FBVs Offer Flexibility FBVs facilitate a great level of flexibility that enables Python developers to utilize any of the Python functions as a view that can also include third-party libraries as well as custom functions. They Are Simple to Understand Function-based views are very easy to understand. Therefore, FBVs are a great choice for small projects as well as simple views. Familiarity Since FBVs utilize a function-based syntax, it will be very easy for Python developers to use them. Python developers are more likely to be comfortable with FBVs as they are already familiar with them. Class-Based Views On the other hand, class-based views (CBVs) offer an abstract class that carries out general development tasks. Here are the benefits of CBVs: Using Structured API On top of object-oriented programming, you can also benefit from utilizing structured API. So as a result, your code will be clearer and readable. The Benefit of Code Reusability CBVs are reusable, and you can extend and modify them with ease with the help of subclassing. It Offers Consistency In order to manage various versions of HTTP requests, CBVs offer a consistent interface. CBVs Are Modular in Nature Since these are modular in nature, you will divide those complicated views into smaller as well as reusable components. 4: Django Developers Write the Application Logic in Views Instead of Model If you write the logic in views, then it will make your application view “fat” and not to mention your model “skinny.” So, it is important that you avoid this mistake and write the application logic in models rather than writing in views. Django developers can further work on breaking the logic into tiny methods, and then they can write them into the models. This will enable them to utilize it various times from multiple sources such as front-end UI, admin interface UI, API endpoints, etc. And they can do it in just a few lines of code. Thus, they will get rid of copying and pasting lots of code. Therefore, when you send an email to a user, you must extend the model with the help of an email function rather than writing the logic in views. Furthermore, this will enable your code to be easily unit-tested. This is because Python developers can test the email logic in one place. So, as a result, they will get rid of testing the email logic over and over again in each controller. Thus, next time you work on your project, write fat models as well as skinny views.
I still remember the day our CTO walked into the engineering huddle and declared, "We're moving everything to Kubernetes." It was 2017, and like many teams caught in the container hype cycle, we dove in headfirst with more excitement than wisdom. What followed was a sobering 18-month journey of steep learning curves, 3 AM incident calls, and the gradual realization that we'd traded one set of operational headaches for another. Fast forward to today, I'm deploying containerized applications without managing a single node. No upgrades. No capacity planning. No security patching. Yet, I still have the full power of Kubernetes' declarative API at my fingertips. The serverless Kubernetes revolution is here, and it's changing everything about how we approach container orchestration. The Evolution I've Witnessed Firsthand Having worked with Kubernetes since its early days, I've lived through each phase of its management evolution: Phase 1: The DIY Era (2015-2018) Our first production Kubernetes cluster was a badge of honor — and an operational nightmare. We manually set up everything: etcd clusters, multiple master nodes for high availability, networking plugins that mysteriously failed, and storage integrations that tested the limits of our patience. We became experts by necessity, learning Kubernetes internals in painful detail. I filled three notebooks with command-line incantations, troubleshooting flows, and architecture diagrams. New team members took weeks to ramp up. We were doing cutting-edge work, but at a staggering operational cost. Phase 2: Managed Control Planes (2018-2020) When GKE, EKS, and AKS matured, it felt like a revelation. "You mean we don't have to manage etcd backups anymore?" The relief was immediate — until we realized we still had plenty of operational responsibilities. Our team still agonized over node sizing, Kubernetes version upgrades, and capacity management. I spent countless hours tuning autoscaling parameters and writing Terraform modules. We eliminated some pain, but our engineers were still spending 20-30% of their time on infrastructure rather than application logic. Phase 3: Advanced Management Tooling (2020-2022) As our company expanded to multiple clusters across different cloud providers, we invested heavily in management layers. Rancher became our control center, and we built automation for standardizing deployments. Tools improved, but complexity increased. Each new feature or integration point added cognitive load. Our platform team grew to five people — a significant investment for a mid-sized company. We were more sophisticated, but not necessarily more efficient. Phase 4: The Serverless Awakening (2022-Present) My epiphany came during a late-night production issue. After spending hours debugging a node-level problem, I asked myself: "Why are we still dealing with nodes in 2022?" That question led me down the path to serverless Kubernetes, and I haven't looked back. What Makes Kubernetes Truly "Serverless"? Through trial and error, I've developed a practical definition of what constitutes genuine serverless Kubernetes: You never think about nodes. Period. No sizing, scaling, patching, or troubleshooting. If you're SSHing into a node, it's not serverless.You pay only for what you use. Our bill now scales directly with actual workload usage. Last month, our dev environment cost dropped 78% because it scaled to zero overnight and on weekends.Standard Kubernetes API. The critical feature that separates this approach from traditional PaaS. My team uses the same YAML, kubectl commands, and CI/CD pipelines we've already mastered.Instant scalability. When our product hit the front page of Product Hunt, our API scaled from handling 10 requests per minute to 3,000 in seconds, without any manual intervention.Zero operational overhead. We deleted over 200 runbooks and automation scripts that were dedicated to cluster maintenance. Real Architectural Approaches I've Evaluated When exploring serverless Kubernetes options, I found four distinct approaches, each with unique strengths and limitations: 1. The Virtual Kubelet Approach We first experimented with Azure Container Instances (ACI) via Virtual Kubelet. The concept was elegant — a virtual node that connected our cluster to a serverless backend. This worked well for batch processing workloads but introduced frustrating latency when scaling from zero. Some of our Kubernetes manifests needed modifications, particularly those using DaemonSets or privileged containers. 2. Control Plane + Serverless Compute Our team later moved some workloads to Google Cloud Run for Anthos. I appreciated maintaining a dedicated control plane (for familiarity) while offloading the compute layer. This hybrid approach provided excellent Kubernetes compatibility. The downside? We still paid for the control plane even when idle, undermining the scale-to-zero economics. 3. On-Demand Kubernetes For our development environments, we've recently adopted an on-demand approach, where the entire Kubernetes environment — control plane included — spins up only when needed. The cost savings have been dramatic, but we've had to architect around cold start delays. We've implemented clever prewarming strategies for critical environments before high-traffic events. 4. Kubernetes-Compatible API Layers I briefly tested compatibility layers that provide Kubernetes-like APIs on top of other orchestrators. While conceptually interesting, we encountered too many edge cases where standard Kubernetes features behaved differently. Platform Experiences: What Actually Worked for Us Rather than providing generic platform overviews, let me share my team's real experiences with these technologies: AWS Fargate for EKS After running Fargate for 14 months, here's my honest assessment: What I loved: The seamless integration with existing EKS deployments lets us migrate workloads gradually. Our developers continued using familiar tools while we eliminated node management behind the scenes. The per-second billing granularity provided predictable costs.What caused headaches: Our monitoring stack relied heavily on DaemonSets, requiring significant rearchitecting. Storage limitations forced us to migrate several stateful services to managed alternatives. Cold starts occasionally impacted performance during low-traffic periods.Pro tip: Create separate Fargate profiles with appropriate sizing for different workload types — we reduced costs by 23% after segmenting our applications this way. Google Cloud Run for Anthos We deployed a new microservice architecture using this platform last year: What worked brilliantly: The sub-second scaling from zero consistently impressed us. The Knative Foundation provided an elegant developer experience, particularly for HTTP services. Traffic splitting for canary deployments became trivially easy.Where we struggled: Building effective CI/CD pipelines required additional work. Some of our batch processing workloads weren't ideal fits for the HTTP-centric model. Cost visibility was initially challenging.Real-world insight: Invest time in setting up detailed monitoring for Cloud Run services. We missed several performance issues until implementing custom metrics dashboards. Azure Container Apps For our .NET-based services, we evaluated Azure Container Apps: Standout features: The built-in KEDA-based autoscaling worked exceptionally well for event-driven workloads. The revisions concept for deployment management simplified our release process.Limitations we encountered: The partial Kubernetes API implementation meant we couldn't directly port all our existing manifests. Integration with legacy on-premises systems required additional networking configuration.Lesson learned: Start with greenfield applications rather than migrations to minimize friction with this platform. Implementation Lessons from the Trenches After transitioning multiple environments to serverless Kubernetes, here are the pragmatic lessons that don't typically make it into vendor documentation: Application Architecture Reality Check Not everything belongs in serverless Kubernetes. Our journey taught us to be selective: Perfect fits. Our API gateways, web frontends, and event processors thrived in serverless environments.Problematic workloads. Our ML training jobs, which needed GPU access and ran for hours, remained on traditional nodes. A database with specific storage performance requirements stayed on provisioned infrastructure.Practical adaptation. We created a "best of both worlds" architecture, using serverless for elastic workloads while maintaining traditional infrastructure for specialized needs. The Cost Model Shift That Surprised Us Serverless dramatically changed our cost structure: Before: Predictable but inefficient monthly expenses regardless of traffic.After: Highly efficient but initially less predictable costs that closely tracked usage.How we adapted: We implemented ceiling limits on autoscaling to prevent runaway costs. We developed resource request guidelines for teams to prevent over-provisioning. Most importantly, we built cost visibility tooling so teams could see the direct impact of their deployment decisions. Developer Experience Transformation Transitioning to serverless required workflow adjustments: Local development continuity. We standardized on kind (Kubernetes in Docker) for local development, ensuring compatibility with our serverless deployments.Troubleshooting changes. Without node access, we invested in enhanced logging and tracing. Distributed tracing, in particular, became essential rather than optional.Deployment pipeline adjustments. We built staging environments that closely mimicked production serverless configurations to catch compatibility issues early. Security Model Adaptation Security practices evolved significantly: Shared responsibility clarity. We documented clear boundaries between provider responsibilities and our security obligations.IAM integration. We moved away from Kubernetes RBAC for some scenarios, leveraging cloud provider identity systems instead.Network security evolution. Traditional network policies gave way to service mesh implementations for fine-grained control. Real-World Outcomes From Our Transition The impact of our serverless Kubernetes adoption went beyond technical architecture: Team Structure Transformation Our platform team of five shrunk to two people, with three engineers reallocated to product development. The remaining platform engineers focused on developer experience rather than firefighting. The on-call rotation, once dreaded for its 3 AM Kubernetes node issues, now primarily handles application-level concerns. Last quarter, we had zero incidents related to infrastructure. Business Agility Improvements Product features that once took weeks to deploy now go from concept to production in days. Our ability to rapidly scale during demand spikes allowed the marketing team to be more aggressive with promotions, knowing the platform would handle the traffic. Perhaps most significantly, we reduced our time-to-market for new initiatives by 40%, giving us an edge over competitors still managing their own Kubernetes infrastructure. Economic Impact After full adoption of serverless Kubernetes: Development environment costs decreased by 78%Overall infrastructure spend reduced by 32%Engineer productivity increased by approximately 25%Time spent on infrastructure maintenance dropped by over 90% Honest Challenges You'll Face No transformation is without its difficulties. These are the real challenges we encountered: Debugging complexity. Without node access, some troubleshooting scenarios became more difficult. We compensated with enhanced observability but still occasionally hit frustrating limitations.Ecosystem compatibility gaps. Several of our favorite Kubernetes tools didn't work as expected in serverless environments. We had to abandon some tooling and adapt others.The cold start compromise. We implemented creative solutions for cold start issues, including keepalive mechanisms for critical services and intelligent prewarming before anticipated traffic spikes.Migration complexity. Moving existing applications required more effort than we initially estimated. If I could do it again, I'd allocate 50% more time for the migration phase. Where Serverless Kubernetes Is Heading Based on industry connections and my own observations, here's where I see serverless Kubernetes evolving: Cost Optimization Tooling The next frontier is intelligent, automated cost management. My team is already experimenting with tools that automatically adjust resource requests based on actual usage patterns. Machine learning-driven resource optimization will likely become standard. Developer Experience Convergence The gap between local development and serverless production environments is narrowing. New tools emerging from both startups and established vendors are creating seamless development experiences that maintain parity across environments. Edge Computing Expansion I'm particularly excited about how serverless Kubernetes is enabling edge computing scenarios. Projects we're watching are bringing lightweight, serverless Kubernetes variants to edge locations with centralized management and zero operational overhead. Hybrid Architectures Standardization The most practical approach for many organizations will be hybrid deployments — mixing traditional and serverless Kubernetes. Emerging patterns and tools are making this hybrid approach more manageable and standardized. Final Thoughts When we started our Kubernetes journey years ago, we accepted operational complexity as the cost of admission for container orchestration benefits. Serverless Kubernetes has fundamentally changed that equation. Today, our team focuses on building products rather than maintaining infrastructure. We deploy with confidence to environments that scale automatically, cost-efficiently, and without operational burden. For us, serverless Kubernetes has delivered on the original promise of containers: greater focus on applications rather than infrastructure. Is serverless Kubernetes right for every workload? Absolutely not. Is it transforming how forward-thinking teams deploy applications? Without question. References Kubernetes Virtual Kubelet documentationCNCF Serverless Landscape AWS Fargate for EKSGoogle Cloud Run for AnthosAzure Container AppsKnative documentation
Welcome to the “Text to Action” series, where we build intelligent systems that transform natural language into real-world actionable outcomes using AI. To understand the concept better, let’s start simple by building a Smart Calendar AI Assistant. Soon, we’ll tackle more complex challenges — from smart home control to document automation — as we master the art of turning words into actions. Our goal for this project is to create a working AI system where you can simply type or say, “Create a party event at 5 pm on March 20” and watch it instantly appear in your Google Calendar. Part 1 focuses on building the backend foundation: an Express.js backend that connects to Google Calendar’s API. This will handle the actual event creation before we add natural language processing in future episodes. Let’s begin by creating our calendar integration! This tutorial shows you how to authenticate with OAuth2 and create a simple Express endpoint that adds events to your Google Calendar — perfect for integrating calendar functionality into any application. Create a simple REST API endpoint that adds events to Google Calendar with minimal code. Links The complete code is available on vivekvells/text-to-calendar-aiThis tutorial video What We're Building A lightweight Express.js API that exposes a single endpoint for creating Google Calendar events. This API will: Authenticate with Google using OAuth2Add events to your primary calendarReturn event details and links Prerequisites JavaScript # Install Node.js (v14+) and npm npm install express googleapis dotenv You'll need OAuth credentials from the Google Cloud Console. Project Structure JavaScript text-to-calendar/ ├── app.js # Our entire application ├── public/ # Static files │ └── index.html # Simple web interface └── .env # Environment variables The Code: Complete Express Application JavaScript // app.js - Google Calendar API with Express require('dotenv').config(); const express = require('express'); const { google } = require('googleapis'); const fs = require('fs'); const path = require('path'); const app = express(); app.use(express.json()); app.use(express.static('public')); // Configure OAuth const oauth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback' ); // Load saved tokens if available try { const tokens = JSON.parse(fs.readFileSync('tokens.json')); oauth2Client.setCredentials(tokens); } catch (e) { /* No tokens yet */ } // Auth routes app.get('/auth/google', (req, res) => { const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/calendar'] }); res.redirect(authUrl); }); app.get('/auth/google/callback', async (req, res) => { const { tokens } = await oauth2Client.getToken(req.query.code); oauth2Client.setCredentials(tokens); fs.writeFileSync('tokens.json', JSON.stringify(tokens)); res.redirect('/'); }); // API endpoint to create calendar event app.post('/api/create-event', async (req, res) => { try { // Check if we have the required fields const { summary, description, startDateTime, endDateTime } = req.body; if (!summary || !startDateTime || !endDateTime) { return res.status(400).json({ error: 'Missing required fields: summary, startDateTime, endDateTime' }); } // Create the calendar event const calendar = google.calendar({ version: 'v3', auth: oauth2Client }); const response = await calendar.events.insert({ calendarId: 'primary', resource: { summary, description: description || summary, start: { dateTime: startDateTime }, end: { dateTime: endDateTime } } }); res.status(201).json({ success: true, eventId: response.data.id, eventLink: response.data.htmlLink }); } catch (error) { console.error('Error creating event:', error); res.status(error.code || 500).json({ error: error.message || 'Failed to create event' }); } }); // Start server const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`)); How to Use 1. Set up Environment Variables Create a .env file: Plain Text GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_SECRET=your_client_secret GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback PORT=3000 2. Authenticate With Google Visit http://localhost:3000/auth/google in your browser to connect to Google Calendar. 3. Create an Event Use a POST request to the API endpoint: JavaScript curl -X POST http://localhost:3000/api/create-event \ -H "Content-Type: application/json" \ -d '{ "summary": "Team Meeting", "description": "Weekly team status update", "startDateTime": "2025-03-10T14:00:00-07:00", "endDateTime": "2025-03-10T15:00:00-07:00" }' Sample response: JavaScript { "success": true, "eventId": "e0ae1vv8gkop6bcbb5gqilotrs", "eventLink": "https://www.google.com/calendar/event?eid=ZTBhZTF2djhna29wNmJjYmI1Z3FpbG90cnMgdml2ZWtzbWFpQG0" } API Endpoint Details POST /api/create-event – Create a calendar event Request: summary (String, required) – Event titledescription (String) – Event detailsstartDateTime (ISO 8601) – When event startsendDateTime (ISO 8601) – When event ends Response format: JavaScript { "success": true, "eventId": "e0ae1vv8gkop6bcbb5gqilotrs", "eventLink": "https://www.google.com/calendar/event?eid=..." } OAuth2 Authentication Flow User visits /auth/google endpoint.User is redirected to the Google consent screen.After granting access, Google redirects to /auth/google/callback. The app stores OAuth tokens for future API calls.API is now ready to create events. Troubleshooting OAuth setup can be tricky. If you encounter issues, refer to the OAUTH_SETUP.md in the repository, which contains a detailed troubleshooting guide for common OAuth errors. Security Considerations Store OAuth tokens securely in production (not in a local file)Use HTTPS for all API endpoints in productionConsider rate limiting to prevent abuseImplement proper error handling and validation Conclusion With just under 60 lines of core code, we've created a functional API that connects to Google Calendar. This foundation can be extended to support more calendar operations like retrieving, updating, or deleting events. The complete code is available on text-to-calendar-ai. The same approach can be applied to integrate with other Google services or third-party APIs that use OAuth2 authentication.
Is Vibe Coding Agile or Merely a Hype?
March 24, 2025
by
CORE
AI-Driven Kubernetes Troubleshooting With DeepSeek and k8sgpt
March 19, 2025
by
CORE
Bringing Security to Digital Product Design
March 18, 2025 by
Hybrid Backup Strategies: On-Premises vs Cloud for DevOps
March 24, 2025 by
Text Clustering With Deepseek Reasoning
March 24, 2025 by
Breaking AWS Lambda: Chaos Engineering for Serverless Devs
March 24, 2025 by
The Role of Sanity Testing in Performance Engineering
March 24, 2025 by
Hybrid Backup Strategies: On-Premises vs Cloud for DevOps
March 24, 2025 by
Breaking AWS Lambda: Chaos Engineering for Serverless Devs
March 24, 2025 by
Breaking AWS Lambda: Chaos Engineering for Serverless Devs
March 24, 2025 by
How to Build a React Native Chat App for Android
March 24, 2025 by
Supercharging Pytest: Integration With External Tools
March 24, 2025 by
Hybrid Backup Strategies: On-Premises vs Cloud for DevOps
March 24, 2025 by
Breaking AWS Lambda: Chaos Engineering for Serverless Devs
March 24, 2025 by
How to Build a React Native Chat App for Android
March 24, 2025 by
Text Clustering With Deepseek Reasoning
March 24, 2025 by
How to Build a React Native Chat App for Android
March 24, 2025 by
Leverage Amazon BedRock Chat Model With Java and Spring AI
March 24, 2025
by
CORE