GenAI has transformed nearly every industry. Our research dives into developer use cases and sentiments on its organizational impact.
Automation and compliance made easy. Learn how to streamline your DevOps processes, enhance compliance and risk mitigation, and get results
Goose Migrations for Smooth Database Changes
A Comprehensive Guide to Building and Debugging Apache Doris
Observability and Performance
The dawn of observability across the software ecosystem has fully disrupted standard performance monitoring and management. Enhancing these approaches with sophisticated, data-driven, and automated insights allows your organization to better identify anomalies and incidents across applications and wider systems. While monitoring and standard performance practices are still necessary, they now serve to complement organizations' comprehensive observability strategies. This year's Observability and Performance Trend Report moves beyond metrics, logs, and traces — we dive into essential topics around full-stack observability, like security considerations, AIOps, the future of hybrid and cloud-native observability, and much more.
Getting Started With Agentic AI
Java Application Containerization and Deployment
On one side, U.S. laws expand data access in the name of national security. On the other hand, French SecNumCloud ensures digital independence for European businesses. Let’s break down the implications of these two models on cybersecurity, compliance, and the protection of critical infrastructure. Part I - Context and Challenges of Data Sovereignty Introduction The USA PATRIOT Act and the French SecNumCloud framework reflect two opposing visions of digital data management. The United States prioritizes national security, with laws allowing extraterritorial access to data stored by American companies. In contrast, France and Europe promote a sovereign and secure approach. Together, they aim to protect sensitive data from foreign interference. The USA PATRIOT Act: Broad Government Access The USA PATRIOT Act was passed in 2001 after the September 11 attacks to expand government agencies' powers in surveillance and counterterrorism. In practice, it grants U.S. authorities broad surveillance capabilities, allowing access to data from companies under American jurisdiction, regardless of where it is stored. The adoption of the CLOUD Act in 2018 further strengthened this authority. It requires American companies to provide data upon request, even if the data is stored on servers located in Europe. The extraterritorial nature of these laws forces American companies to hand over data to U.S. authorities, including data stored in Europe. This creates a direct conflict with the GDPR. For European businesses using American cloud services, it opens the door to potential surveillance of their strategic and sensitive data. Beyond confidentiality concerns, this situation raises a real challenge to digital sovereignty, as it questions Europe’s ability to manage its own data independently and securely. SecNumCloud: Strengthening Digital Sovereignty In response to these challenges, France developed SecNumCloud, a cybersecurity certification issued by ANSSI (the National Cybersecurity Agency in France). It ensures that cloud providers adhere to strict security and data sovereignty standards. SecNumCloud-certified providers must meet strict requirements to safeguard data integrity and sovereignty against foreign interference. First, cloud infrastructure and operations must remain entirely under European control, ensuring no external influence — particularly from the United States or other third countries — can be exerted. Additionally, no American company can hold a stake or exert decision-making power over data management, preventing any legal obligation to transfer data to foreign authorities under the CLOUD Act. Just as importantly, clients retain full control over access to their data. They are guaranteed that their data cannot be used or transferred without their explicit consent. With these measures, SecNumCloud prevents foreign interference and ensures a sovereign cloud under European control, fully compliant with the GDPR. This allows European businesses and institutions to store and process their data securely, without the risk of being subject to extraterritorial laws like the CLOUD Act. SecNumCloud ensures strengthened digital sovereignty by keeping data under exclusive European jurisdiction, shielding it from extraterritorial laws like the CLOUD Act. This certification is essential for strategic sectors such as public services, healthcare, defense, and Operators of Vital Importance (OIVs), thanks to its compliance with the GDPR and European regulations. OIV (Operators of Vital Importance) OIVs refer to public or private entities in France deemed essential to a nation’s functioning, such as energy infrastructure, healthcare systems, defense, and transportation. Their status is defined by the French Interministerial Security Framework for Vital Activities (SAIV), established in the Defense Code. OSE (Operators of Essential Services) Established under the EU NIS Directive (Network and Information Security), OSEs include companies providing critical services to society and the economy, such as banks, insurance providers, and telecommunications firms. Their reliance on information systems makes them particularly vulnerable to cyberattacks. Why It Matters OIVs and OSEs are central to national cybersecurity strategy in France. A successful attack on these entities could have major consequences for a country’s infrastructure and economy. This is why strict regulations and regular monitoring are enforced to ensure their resilience against digital threats. GDPR and the AI Act: Safeguarding Digital Sovereignty The GDPR (General Data Protection Regulation) imposes strict obligations on businesses regarding data collection, storage, and processing, with heavy penalties for non-compliance. The AI Act, currently being adopted by the European Union, complements this framework by regulating the use of artificial intelligence to ensure ethical data processing and protect users. Together, these regulations play a key role in governing digital technologies and increase pressure on businesses to adopt cloud infrastructures that comply with European standards, further strengthening the continent’s digital sovereignty. Part II - SecNumCloud: A Cornerstone to Digital Sovereignety Sovereign Cloud: Key Challenges and Considerations Cloud computing is a major strategic and economic issue. Dependence on American tech giants exposes European data to cybersecurity risks and foreign interference. To mitigate these risks, SecNumCloud ensures the protection of critical data and enforces strict security standards for cloud providers operating under European jurisdiction. SecNumCloud: Setting the Standard for Secure Cloud Services ANSSI designed SecNumCloud as a sovereign response to the CLOUD Act. Today, several French cloud providers, including Outscale, OVHcloud, and S3NS, have adopted this certification. SecNumCloud could serve as a blueprint for the EUCS (European Cybersecurity Certification Scheme for Cloud Services), which seeks to create a unified European standard for a sovereign and secure cloud. A Key Priority for the Public Sector and Critical Infrastructure Operators of Vital Importance (OIVs) and Operators of Essential Services (OSEs), which manage critical infrastructure (energy, telecommunications, healthcare, and transportation), are prime targets for cyberattacks. For example, in 2020, a cyberattack targeted a French hospital and paralyzed its IT infrastructure for several days. This attack jeopardized patient management. Using a sovereign cloud certified by SecNumCloud would have strengthened the hospital’s protection against such an attack by providing better security guarantees and overall greater resilience against cyber threats. Building a European Sovereign Cloud As SecNumCloud establishes itself as a key framework in France, it could serve as a European model. Through the EUCS initiative, the European Union aims to set common standards for a secure and independent cloud, protecting sensitive data from foreign interference. Within this framework, SecNumCloud goes beyond being just a technical certification. It aims to establish itself as a strategic pillar in strengthening Europe’s digital sovereignty and ensuring the resilience of its critical infrastructure. Conclusion The adoption of SecNumCloud is now a strategic priority for all organizations handling sensitive data. By ensuring protection against extraterritorial laws and full compliance with European regulations, SecNumCloud establishes itself as a key pillar of digital sovereignty. Thanks to key players like Outscale, OVH, and S3NS, France and Europe are laying the foundation for a sovereign, secure, and resilient cloud capable of withstanding foreign threats. One More Thing: A Delicate Balance Between Security and Sovereignty If digital sovereignty and data protection are priorities for Europe, it appears essential to place this debate within a broader context. U.S. Security Indeed, U.S. laws address legitimate security concerns. The United States implemented these laws in the context of counterterrorism and cybercrime prevention. The goal of the PATRIOT Act and the CLOUD Act is to enhance intelligence agency cooperation and ensure national security against transnational threats. In this context, American companies have little choice. Cloud giants like Microsoft, Google, and Amazon, to name a few, do not voluntarily enforce the CLOUD Act — they are legally required to comply. Even though they strive to ensure customer data confidentiality, they must adhere to U.S. government requests, even at the risk of conflicting with European laws such as the GDPR. EU Sovereignty Europe does not seek isolation but rather aims for self-reliance in security. The adoption of SecNumCloud and the GDPR is not about blocking American technologies, but about guaranteeing that European companies and institutions keep full authority over their sensitive data. This strategy ensures long-term technological independence while promoting collaboration that respects each region’s legal frameworks. This debate should not be seen as a confrontation between Europe and the United States, but rather as a global strategic challenge: how to balance international security and digital sovereignty in an increasingly interconnected world?
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. Modernizing Enterprise Java Applications: Jakarta EE, Spring Boot, and AI Integration Date: February 25, 2025Time: 1:00 PM ET Register for Free! Unlock the potential of AI integration in your enterprise Java applications with our upcoming webinar! Join Payara and DZone to explore how to enhance your Spring Boot and Jakarta EE systems using generative AI tools like Spring AI and REST client patterns. What to Consider When Building an IDP Date: March 4, 2025Time: 1:00 PM ET Register for Free! Is your development team bogged down by manual tasks and “TicketOps”? Internal Developer Portals (IDPs) streamline onboarding, automate workflows, and enhance productivity—but should you build or buy? Join Harness and DZone for a webinar to explore key IDP capabilities, compare Backstage vs. managed solutions, and learn how to drive adoption while balancing cost and flexibility. DevOps for Oracle Applications with FlexDeploy: Automation nd Compliance Made Easy Date: March 11, 2025Time: 1:00 PM ET Register for Free! Join Flexagon and DZone as Flexagon's CEO unveils how FlexDeploy is helping organizations future-proof their DevOps strategy for Oracle Applications and Infrastructure. Explore innovations for automation through compliance, along with real-world success stories from companies who have adopted FlexDeploy. Make AI Your App Development Advantage: Learn Why and How Date: March 12, 2025Time: 10:00 AM ET Register for Free! The future of app development is here, and AI is leading the charge. Join Outsystems and DZone, on March 12th at 10am ET, for an exclusive Webinar with Luis Blando, CPTO of OutSystems, and John Rymer, industry analyst at Analysis.Tech, as they discuss how AI and low-code are revolutionizing development.You will also hear from David Gilkey, Leader of Solution Architecture, Americas East at OutSystems, and Roy van de Kerkhof, Director at NovioQ. This session will give you the tools and knowledge you need to accelerate your development and stay ahead of the curve in the ever-evolving tech landscape. Developer Experience: The Coalescence of Developer Productivity, Process Satisfaction, and Platform Engineering Date: March 12, 2025Time: 1:00 PM ET Register for Free! Explore the future of developer experience at DZone’s Virtual Roundtable, where a panel will dive into key insights from the 2025 Developer Experience Trend Report. Discover how AI, automation, and developer-centric strategies are shaping workflows, productivity, and satisfaction. Don’t miss this opportunity to connect with industry experts and peers shaping the next chapter of software development. 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. 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!
Indexes are the objects of MS SQL database files. These schema objects act like a table of contents of the database. These are used to improve the data retrieval operations on an MS SQL database table. However, with time, the table can get fragmented due to continuous INSERT and UPDATE operations. Also, like other objects, indexes are also prone to corruption. If the indexes get corrupted, you can receive errors like: Plain Text Table error: table 'Items' (ID 1954106002). Data row does not have a matching index row in the index 'IX_Advertise_BrandCopy_Price_Stock' (ID 69). Possible missing or invalid keys for the index row matching: Msg 8955, Level 16, State 1, Line 1 Data row (1:11226494:9) identified by (Id = 11078215) with index values 'AdvertiseFlag = 1 and BrandCopyParentId = 0 and NSFPPrice = 137.50 and NSFPQtyInStock = 0 and Id = 11078215'. Msg 8951, Level 16, State 1, Line 1 To resolve the errors associated with corruption in indexes or when the fragmentation level of the indexes increases, you can rebuild or reorganize the indexes. In this article, we'll discuss how to reorganize and rebuild the indexes in MS SQL Server. We'll also mention an advanced MS SQL database repair tool that can restore indexes and other objects from corrupted SQL database files quickly with complete precision. Check the Fragmentation Percentage of Indexes Before reorganizing the indexes, you need to know the percentage of fragmentation. You can use the below command to check the percentage of the index fragmentation: SQL SELECT OBJECT_NAME(object_id) tableName12, index_id, index_type_desc, avg_fragmentation_in_percent, page_count FROM sys.dm_db_index_physical_stats ( DB_ID('Adventureworks2019'), OBJECT_ID('[Person].[Person]'), NULL, NULL, 'DETAILED' ); Reorganizing Indexes in SQL Server If the fragmentation level of the index is between 10 and 30, it is recommended to reorganize the index. You can use the following code in T-SQL to reorganize the index: SQL ALTER INDEX [PK_Person_BusinessEntityID] ON [Person].[Person] REORGANIZE; Alternatively, you can use the SQL Server Management Studio (SSMS) to reorganize the indexes. Follow the below steps: In SSMS, connect to your SQL Server instance.In Object Explorer, expand the database.Expand the Tables folder and then the Indexes folder.Right-click on the index you need to reorganize, and then click Reorganize. Rebuilding Indexes in MS SQL If the fragmentation is higher, then you can rebuild the indexes. You can use the DBCC DBREINDEX command to rebuild an index. Here’s how to execute this command: SQL DBCC DBREINDEX ( table_name [ , index_name [ , fillfactor ] ] ) [ WITH NO_INFOMSGS ] This command does not support spatial indexes and memory-optimized column store indexes. Alternatively, you can use the ALTER INDEX command: SQL ALTER INDEX [PK_Person_BusinessEntityID] ON [Person].[Person] REBUILD; You can also rebuild the indexes by using the graphical user interface in SSMS. Here are the steps: In SSMS, in Object Explorer, expand the database containing the table on which you need to rebuild an index.Expand the Tables folder and then the table on which you need to rebuild an index.Expand the Indexes folder, right-click the index you need to rebuild, and select Rebuild. Repair the SQL Server Database If reorganizing or rebuilding the indexes does not work, then you can try the DBCC CHECKDB command with the REPAIR_ALLOW_DATA_LOSS option to repair the database. Here’s how to use this command: First, change the mode of the database to SINGLE_USER by using the below command: SQL ALTER DATABASE BusinessEntity SET SINGLE_USER Next, run the DBCC CHECKDB command as given below to repair the database: SQL DBCC CHECKDB (N ’BusinessEntity’, REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS, NO_INFOMSGS; GO After repair, set the database mode to MULTI_USER by executing the below command: SQL ALTER DATABASE BusinessEntity SET MULTI_USER The above DBCC CHECKDB command can help you resolve all the errors that are related to corruption in indexes. It can repair both clustered and non-clustered indexes in SQL databases. However, it does not guarantee complete data recovery and can result in data loss. To prevent data loss, you can use any professional MS SQL repair tools. They are designed with advanced algorithms to recover all the objects, including clustered and non-clustered indexes, stored procedures, triggers, etc., from the corrupt database. Conclusion You can reduce index fragmentation by reorganizing or rebuilding the index. In this article, we have explained how to check the fragmentation percentage in indexes and how to rebuild the indexes.
CRUD operations are fundamental building blocks and crucial for managing data. They are ubiquitous in virtually every application, from simple websites to complex enterprise solutions. NestJS Boilerplate users have already been able to evaluate and use a powerful new tool — CLI, which allows you to automatically create resources and their properties. With this tool, you can make all CRUD operations and add the necessary fields to them without writing a single line of code manually. Let’s now explore CRUD operations from the frontend perspective. In Next.js, a React framework with server-side rendering capabilities, these operations can be efficiently managed with features that enhance performance, SEO, and developer experience. Previously, we published an article about an effective way to start a NextJS project, and now we want to go further and analyze the details and nuances of working with the APIs in Next.js. As we know, the acronym CRUD stands for Create, Read, Update, and Delete. This concept represents the fundamental operations that can be performed on any data. Let's consider working with CRUD operations using the example of the administrative panel user, where functionalities like adding, editing, and deleting users are implemented, along with retrieving information about them. The custom React hooks discussed below, handling data processing in React Query, pagination, error management, and more, are already integrated into the Extensive-React-Boilerplate. Naturally, you can leverage this boilerplate directly. In the following sections, we’ll share our insights on implementing these features. Create Operation Use Case Submitting data to create a new resource (e.g., user registration, adding a new product). Implementation Collect data from the form, send a POST request to the server, handle the response, and update the UI accordingly. Let’s observe an example. Making a POST request to the API is incorporated, creating a new user. In the snippet below, the usePostUserService hook is used to encapsulate this logic. We’ve specified the data structure for creating a new user by defining the request and response types, but omit this part here to help you focus. You can see more detailed information or a more complete picture in the repository Extensive-React-Boilerplate because this and all the following code snippets are from there. So, we’ll create a custom hook usePostUserService that uses the useFetch hook to send a POST request. It takes user data as input and sends it to the API: TypeScript function usePostUserService() { const fetch = useFetch(); return useCallback( (data: UserPostRequest, requestConfig?: RequestConfigType) => { return fetch(`${API_URL}/v1/users`, { method: "POST", body: JSON.stringify(data), ...requestConfig, }).then(wrapperFetchJsonResponse<UserPostResponse>); }, [fetch] ); } The function wrapperFetchJsonResponse will be examined later in this article when we get to "error handling." Read Operations Use Case Fetching and displaying a list of resources or a single resource (e.g., fetching user profiles and product lists). Implementation Send a GET request to fetch data, handle loading and error states, and render the data in the UI. In our example, reading data involves making GET requests to the API to fetch user data. It can include fetching all users with pagination, filters, and sorting or fetching a single user by ID after defining the request (UsersRequest) and response types (UsersResponse). To fetch all users in the custom useGetUsersService hook, we send a GET request with query parameters for pagination, filters, and sorting: TypeScript function useGetUsersService() { const fetch = useFetch(); return useCallback( (data: UsersRequest, requestConfig?: RequestConfigType) => { const requestUrl = new URL(`${API_URL}/v1/users`); requestUrl.searchParams.append("page", data.page.toString()); requestUrl.searchParams.append("limit", data.limit.toString()); if (data.filters) { requestUrl.searchParams.append("filters", JSON.stringify(data.filters)); } if (data.sort) { requestUrl.searchParams.append("sort", JSON.stringify(data.sort)); } return fetch(requestUrl, { method: "GET", ...requestConfig, }).then(wrapperFetchJsonResponse<UsersResponse>); }, [fetch] ); } For fetching a single user, the useGetUserService hook sends a GET request to fetch a user by ID: TypeScript function useGetUserService() { const fetch = useFetch(); return useCallback( (data: UserRequest, requestConfig?: RequestConfigType) => { return fetch(`${API_URL}/v1/users/${data.id}`, { method: "GET", ...requestConfig, }).then(wrapperFetchJsonResponse<UserResponse>); }, [fetch] ); } Update Operation Use Case Editing an existing resource (e.g., updating user information, editing a blog post). Implementation Collect updated data, send a PUT or PATCH request to the server, handle the response, and update the UI. Let’s carry out updating an existing user, which involves sending a PATCH request to the API with the updated user data. For this, in the custom usePatchUserService hook, we send a PATCH request with the user ID and updated data after defining the request UserPatchRequest and response types UserPatchResponse: TypeScript function usePatchUserService() { const fetch = useFetch(); return useCallback( (data: UserPatchRequest, requestConfig?: RequestConfigType) => { return fetch(`${API_URL}/v1/users/${data.id}`, { method: "PATCH", body: JSON.stringify(data.data), ...requestConfig, }).then(wrapperFetchJsonResponse<UserPatchResponse>); }, [fetch] ); } Note: Using PATCH instead of PUT is more advanced for partial data updates, while PUT is typically used for full resource updates. Delete Operation Use Case Removing a resource (e.g., deleting a user or removing an item from a list). Implementation Send a DELETE request to the server, handle the response, and update the UI to reflect the removal. In our next example, deleting a user involves sending a DELETE request to your API with the user ID. After defining the request (UsersDeleteRequest) and response types (UsersDeleteResponse) in the useDeleteUsersService hook, a DELETE request is transmitted to remove the user by ID. TypeScript function useDeleteUsersService() { const fetch = useFetch(); return useCallback( (data: UsersDeleteRequest, requestConfig?: RequestConfigType) => { return fetch(`${API_URL}/v1/users/${data.id}`, { method: "DELETE", ...requestConfig, }).then(wrapperFetchJsonResponse<UsersDeleteResponse>); }, [fetch] ); } These hooks abstract the complexity of making HTTP requests and handling responses. Using such an approach ensures a clean and maintainable codebase, as the data-fetching logic is encapsulated and reusable across your components. Retrieving Data in Next.js Ok, we have dealt with examples of processing CRUD operations, and let's take a closer look at the methods of obtaining data offered by Next.js because it, as a framework, adds its functions and optimizations over React. It is clear that Next.js, beyond CSR (client-side rendering), provides advanced features like SSR (server-side rendering), SSG (static site generation), built-in API routes, and hybrid rendering. So, let's discuss commonalities and differences in retrieving data in Next.js and React. As soon as React apps are purely client-side, so data fetching happens on the client after the initial page load. For dynamic pages that need to fetch data every time a page is loaded, it is more suitable to use SSR. In this case, data is fetched on the server at the request time. In the case of SSG, which is suitable for static pages where data doesn’t change often, data is fetched at build time. So, the getStaticProps method helps us to fetch data at build time (SSG). If we need pages to be pre-render based on dynamic routes and the data fetched at build time, the getStaticPaths method is allowing to do this. It is used in conjunction with the getStaticProps to generate dynamic routes at build time. It should be noted that starting with Next 14, we can make requests directly in components without these methods, which gives a more "React experience." Client-side data fetching with useQuery can be used for interactive components that need to fetch data on the client side, with the initial state hydrated from server-side fetched data. For fetching data that changes frequently or for adding client-side interactivity, the 'useSWR' strategy is useful. It’s a React hook for client-side data fetching with caching and revalidation. It allows fetching data on the client side, usually after the initial page load. Nevertheless, it does not fetch data at build time or on the server for SSR, but it can revalidate and fetch new data when required. To summarize the information about the methods above, we can take a look at the table that provides a comprehensive overview of the different data fetching methods in Next.js, highlighting their respective timings and use cases. MethodData fetchingtiminguse casegetStaticPathsStatic site generation (SSG)At build timePre-render pages for dynamic routes based on data available at build time.getStaticPropsStatic site generation (SSG)At build timePre-render pages with static content at build time. Ideal for content that doesn't change frequently.getServerSidePropsServer-side rendering (SSR)On each requestFetch data on the server for each request, providing up-to-date content. Ideal for dynamic content that changes frequently.useQueryClient-side rendering (CSR)After the initial page loadFetch initial data server-side, hydrate, reduce redundant network requests, background refetching.useSWRClient-side rendering (CSR)After the initial page loadFetch and revalidate data on the client side, suitable for frequently changing data. Using React Query With Next.js React Query provides hooks for fetching, caching, synchronizing, and updating server-state, making it a great tool for handling data in both React and Next.js applications. Key benefits of its use are: Efficient data fetching. It handles caching and background data synchronization, reducing redundant network requests.Automatic refetching. Data can be automatically refetched in the background when it becomes stale, ensuring that the UI always displays the latest information.Integrated error handling. Built-in support for handling errors and retries, making it easier to manage network failures and server errors.Optimistic updates. The useMutation hook provides optimistic updates by providing an easy way to handle both the optimistic UI changes and rollback logic if the server request fails.Ease of integration with Next.js. It can be seamlessly integrated with other Next.js data fetching methods like getStaticProps or getServerSideProps (if needed).Inspection of query and mutation. The ReactQueryDevtools tool provides the possibility of viewing the status, data, errors, and other details of all active queries and mutations and watching the query states update in real time as your application runs. QueryClientProvider QueryClientProvider is a context provider component that supplies a QueryClient instance to the React component tree. This instance is necessary for using hooks like useQuery. To set it up, it needs to be placed at the root of your component tree and configure global settings for queries and mutations like retry behavior, cache time, and more. After this, it initializes the React Query client and makes it available throughout the application. TypeScript import ReactQueryDevtools from "@/services/react-query/react-query-devtools"; ... export default function RootLayout({ ... }) { return ( <html lang={language} dir={dir(language)}> <body> <InitColorSchemeScript ></InitColorSchemeScript> <QueryClientProvider client={queryClient}> <ReactQueryDevtools initialIsOpen={false} ></ReactQueryDevtools> ... </QueryClientProvider> </body> </html> ); } So, why should it be added to the project? It is beneficial for: Centralized configuration for all queries and mutationsEasy to set up and integrate into existing React applicationsEnables features like caching, background refetching, and query invalidation React Query Devtools The other important feature provided by React Query is ReactQueryDevtools — a development tool for inspecting and debugging React Query states. It can be easily added to your application and accessed via a browser extension or as a component like in the example before. During development, React Query Devtools can be used for inspection of individual queries and mutations, understanding why certain queries are prefetching, monitoring the state of the query cache, and seeing how it evolves. Pagination and Infinite Scrolling To implement pagination controls or infinite scrolling using features in libraries, useInfiniteQuery is a perfect fit. First, we generate unique keys for caching and retrieving queries in React Query. The by method here creates a unique key based on the sorting and filtering options. TypeScript const usersQueryKeys = createQueryKeys(["users"], { list: () => ({ key: [], sub: { by: ({ sort, filter, }: { filter: UserFilterType | undefined; sort?: UserSortType | undefined; }) => ({ key: [sort, filter], }), }, }), }); To do this, we will use the useInfiniteQuery function from React Query and take the useGetUsersService hook discussed above in the Read Operations section. TypeScript export const useUserListQuery = ({ sort, filter, }: { filter?: UserFilterType | undefined; sort?: UserSortType | undefined; } = {}) => { const fetch = useGetUsersService(); const query = useInfiniteQuery({ queryKey: usersQueryKeys.list().sub.by({ sort, filter }).key, initialPageParam: 1, queryFn: async ({ pageParam, signal }) => { const { status, data } = await fetch( { page: pageParam, limit: 10, filters: filter, sort: sort ? [sort] : undefined, }, { signal, } ); if (status === HTTP_CODES_ENUM.OK) { return { data: data.data, nextPage: data.hasNextPage ? pageParam + 1 : undefined, }; } }, getNextPageParam: (lastPage) => { return lastPage?.nextPage; }, gcTime: 0, }); return query; }; The QueryFn here retrieves the user data based on the current page, filter, and sort parameters, and the getNextPageParam function determines the next page to fetch based on the response of the last page. When the user scrolls or requests more data, useInfiniteQuery automatically retrieves the next set of data based on the nextPage parameter — this is how infinite scrolling happens. The cache time for the query is set by the gcTime parameter. Overall, React Query provides a comprehensive solution for managing and debugging server-state in React applications. QueryClientProvider ensures a centralized and consistent configuration for all queries and mutations, while ReactQueryDevtools offers powerful tools for inspecting and understanding query behavior during development. Error Handling Implementing CRUD operations always requires proper error handling to ensure user-friendliness and application reliability. Server errors are usually associated with failed processing of a client request, errors in server code, resource overload, infrastructure misconfiguration, or failures in external services. For error handling, Extensive-React-Boilerplate suggests using the wrapperFetchJsonResponse function: TypeScript async function wrapperFetchJsonResponse<T>( response: Response ): Promise<FetchJsonResponse<T>> { const status = response.status as FetchJsonResponse<T>["status"]; return { status, data: [ HTTP_CODES_ENUM.NO_CONTENT, HTTP_CODES_ENUM.SERVICE_UNAVAILABLE, HTTP_CODES_ENUM.INTERNAL_SERVER_ERROR, ].includes(status) ? undefined : await response.json(), }; } Conclusion In this article, we covered the fundamental CRUD operations and explored data retrieval techniques in NextJS. We delved into using React Query to manage state, also outlining the capabilities of QueryClientProvider and ReactQueryDevtools for debugging and optimizing data retrieval. Additionally, we discussed implementing pagination and infinite scrolling to handle large datasets and addressed error handling to make your applications more resilient and ensure a smooth user experience. Following the examples and techniques outlined in this article will help you be well-equipped to handle CRUD operations in your NextJS projects. Alternatively, you can use our Extensive-react-boilerplate template for your project. It has a fully compatible nestjs-boilerplate backend that implements the ability to work with CRUD operations in minutes, without a single line of code using the CLI, we've covered this in more detail here and here for entity relationships. Keep experimenting, stay updated with best practices, and welcome to try this boilerplate if you find it useful.
As artificial intelligence gets more advanced, robots are increasingly used to free humans from the risks of inspecting dangerous locations or the drudgery of routine visual surveillance. To provide some degree of autonomous decision, the robot often has cameras connected to an onboard Linux computer such as an NVIDIA Jetson or x86 single-board computer (SBC). If the application benefits from live 360-degree video, there are two approaches. The first, more difficult approach is to use multiple cameras and stitch the video together on the computer or process each video feed separately. The number of cameras used depends on the field of view of each camera. If the robot uses two fisheye lenses that can show more than 180 degrees, only two cameras are needed. It's common to use three or four cameras. A simpler approach is to use a consumer 360° camera. On the free and independent theta360.guide developer community, one of the most popular topics developers discuss is how to add 360° vision to their robot prototype and feed the video into OpenCV. Although there are different ways to get the video from the 360° camera to the Linux computer, the most popular method is to use a USB cable. WiFi and Ethernet are not as popular. The RICOH THETA V, X, and Z1 models can appear as a USB webcam to the Linux computer. However, a bit of work is required on Linux. MacOS computers can use the RICOH THETA camera directly. For Windows, RICOH provides a driver. For Linux, RICOH provides a modified version of libuvc on GitHub called libuvc-theta. To expose the camera as a video device to OpenCV, RICOH provides a sample program, libuvc-theta-sample. This can be used with v4l2loopback to make a /dev/video* device available to OpenCV. To test the camera to Linux computer connection, first attach a USB cable from the camera to the Linux computer and run these steps: Shell git clone https://github.com/ricohapi/libuvc-theta.git sudo apt install libjpeg-dev cd libuvc-theta mkdir build cd build cmake .. make sudo make install cd ../.. git clone https://github.com/ricohapi/libuvc-theta-sample.git cd libuvc-theta-sample/gst make # THETA must be plugged into your computer and in # live streaming mode ./gst_viewer 360° video camera displayed with gstreamer If the build fails, you may need additional gstreamer libraries. This will install all the libraries. Shell sudo apt-get install \ libgstreamer1.0-0 \ gstreamer1.0-plugins-base \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad \ gstreamer1.0-plugins-ugly \ gstreamer1.0-libav \ gstreamer1.0-doc \ gstreamer1.0-tools \ gstreamer1.0-x \ gstreamer1.0-alsa \ gstreamer1.0-gl \ gstreamer1.0-gtk3 \ gstreamer1.0-qt5 \ gstreamer1.0-pulseaudio \ libgstreamer-plugins-base1.0-dev Additionally, a Raspberry Pi 4 will not work due to a lack of 4K H.264 hardware decoding support. Setting Up /dev/video* To make the libuvc device available as a video device, we can use v4l2loopback. The libuvc-theta-sample application will use gstreamer to sink the video to v4l2. If you only have a single video camera on your computer, then you should modify this line prior to running the build. The line below is for a computer with the RICOH THETA connected to it as the only webcam: Plain Text "v4l2sink device=/dev/video0 sync=false"; Access From OpenCV To test whether or not the camera can be used from OpenCV, let's start with a simple frame resize. Python import cv2 cap = cv2.VideoCapture(0) # Check if the webcam is opened correctly if not cap.isOpened(): raise IOError("Cannot open webcam") while True: ret, frame = cap.read() frame = cv2.resize(frame, None, fx=0.25, fy=0.25, interpolation=cv2.INTER_AREA) cv2.imshow('Input', frame) c = cv2.waitKey(1) if c == 27: break cap.release() cv2.destroyAllWindows() Camera Latency Latency is a big issue in the community discussions. The THETA camera latency is approximately 350ms. This is too high for many applications that require the robot to react quickly. It is also too high for telepresence if you're controlling a drone that is moving around obstacles. The latency is due to the in-camera stitching of the two lenses. The stitching cannot be disabled on the video feed. Alternative to v4l2loopback As v4l2loopback adds complexity and overhead, the gstthetauvc package can be used to access the camera directly from gstreamer. Here's the same test using gstthetauvc. Python import cv2 # pipeline below worked # cap = cv2.VideoCapture("thetauvcsrc \ # ! decodebin \ # ! autovideoconvert \ # ! video/x-raw,format=BGRx \ # ! queue ! videoconvert \ # ! video/x-raw,format=BGR ! queue ! appsink") # pipeline suggestion thanks to nickel110 # attempt to force hardware acceleration # tested with NVIDIA 510.73 with old GTX 950 on Ubuntu 22.04 cap = cv2.VideoCapture("thetauvcsrc \ ! queue \ ! h264parse \ ! nvdec \ ! gldownload \ ! queue \ ! videoconvert n-threads=0 \ ! video/x-raw,format=BGR \ ! queue \ ! appsink") if not cap.isOpened(): raise IOError('Cannot open RICOH THETA') while True: ret, frame = cap.read() frame = cv2.resize(frame, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA) cv2.imshow('frame', frame) c = cv2.waitKey(1) if c == 27: break cap.release() cv2.destroyAllWindows() Examples of 360 Video Live Feed Here are some examples of using OpenCV for color transformation and edge detection. Python # Convert to graycsale img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Blur the image for better edge detection img_blur = cv2.GaussianBlur(img_gray, (3,3), 0) # Canny Edge Detection edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) # Canny Edge Detection # Display Canny Edge Detection Image cv2.imshow('Canny Edge Detection', edges) Live person and monitor detection. The camera has two lenses. The side opposite the shutter button is the rear lens and is will be placed in the center of the scene. The object detection is most accurate when the object is centered in the equirectangular view. Human centered on rear lens However, the detection works reasonably well when the human is at the edge of the equirectangular view, even at night. You may need to train your model on a database of fisheye or 360° images. Human detected on edge of equirectangular view Improving Detection Now that you have basic detection working in equirectangular frames from a live 360° video feed, you can experiment with different techniques to improve accuracy. One method is to create multiple flattened scenes from each frame and then detect the object in each scene. The video 360° Object Detect(Yolo) in ROS2 rviz2 and KR260 by misoji engineer provides a good demonstration of this technique. There is also a version of YOLO that has been modified for use with panoramic video. Refer to the paper PV-YOLO: An Object Detection Model for Panoramic Video based on YOLOv4. Conclusion Using a consumer off-the-shelf 360° camera, you can fairly easily get the 360° video to appear as a USB webcam to software such as OpenCV. With the 360° video accessible either as a video device or accessed through gstreamer, standard OpenCV or other detection software can be used on the equirectangular frames of the live video. Due to distortion of the image, the object detection algorithm will not work as well as compared to a standard non-360° webcam. However, even without modification, you can still get started with some detection. To actually use the detection for more than an educational or research project, you will need to look into techniques to improve recognition of the objects in the distorted equirectangular view.
Microsoft Azure SQL is a robust, fully managed database platform designed for high-performance querying, relational data storage, and analytics. For a typical web application with a backend, it is a good choice when we want to consider a managed database that can scale both vertically and horizontally. An application software generates user metrics on a daily basis, which can be used for reports or analytics. Azure SQL is a great choice to consider for storing and querying this data under certain conditions: The analytical queries require joins with other tables (applying filters on UI)You want to combine historical and transactional dataThe data volume is not extremely large, and query performance can be managed by tuning Let's consider an example of a hotel booking site running Azure SQL in the backend. We want to see a UI dashboard for tracking user activity, such as clicks on the site, visits to the hotel description page, bookings made, etc. Let's assume all this telemetry data is dumped for each user on a daily basis in unstructured storage, and we are pulling this data into our database using background jobs, such as Apache Airflow. Below is the schema for users table and a table to store daily metrics. MS SQL CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, age INT , city VARCHAR(100), country VARCHAR(100), currency VARCHAR(10), last_login VARCHAR, hotel_preference VARCHAR(100) ); CREATE TABLE daily_user_metrics ( id BIGINT IDENTITY PRIMARY KEY, -- Unique identifier for the record user_id BIGINT NOT NULL, -- Foreign key to the users table clicks INT DEFAULT 0, -- Number of total site clicks visits INT DEFAULT 0, -- Number of visits to the hotel page bookings INT DEFAULT 0, -- Number of bookings reviews INT DEFAULT 0, -- Number of reviews cancellations INT DEFAULT 0, -- Number of cancellations date_created DATE, -- Daily metrics for each user ); You can draw many insights from the above tables. Let's consider one particular example. We need to aggregate daily activity metrics grouped by date in descending order for customers between the ages of 30 and 40 located in New York City. Below is the query: MS SQL SELECT date_created, SUM(clicks) AS total_clicks, SUM(visits) AS total_visits, SUM(bookings) AS total_bookings, SUM(reviews) AS total_reviews, SUM(cancellations) AS total_cancellations, FROM daily_user_metrics m INNER JOIN users u on m.user_id = u.id WHERE u.age BETWEEN 30 and 40 and u.city ='New York' and m.date_created BETWEEN :startDate and :endDate GROUP BY date_created ORDER BY date_created DESC Now, we can analyze the daily trends from this group of users who are in New York and between the ages of 30 and 40. The table is mostly performant, and we are able to easily perform range queries spread across multiple months. Eventually, our requirements grow. We now want to track user behavior in a weekly or monthly range. But our table stores the data on a daily basis. We now have two options: Query the table and group the date_created weekly or monthly, depending on the ask. Create a couple of views that aggregate the data on a weekly or monthly basis per user. See the query below: MS SQL CREATE VIEW weekly_user_metrics AS SELECT DATEADD(DAY, -(DATEPART(WEEKDAY, date) - 1), date) AS week_start, -- Start of the week (Sunday) SUM(clicks) AS total_clicks, SUM(visits) AS total_visits, SUM(bookings) AS total_bookings, SUM(reviews) AS total_reviews, SUM(cancellations) AS total_cancellations, FROM daily_user_metrics m INNER JOIN users u on m.user_id = u.id WHERE u.age BETWEEN 30 and 40 and u.city ='New York' and m.date_created BETWEEN :startDate and :endDate GROUP BY DATEADD(DAY, -(DATEPART(WEEKDAY, date) - 1), date) -- Group by week start ORDER BY DATEADD(DAY, -(DATEPART(WEEKDAY, date) - 1), date) DESC -- Sort by latest week However, one important thing to consider is that views just provide an abstraction to the underlying query which simply queries the underlying table. Materialized Views are the next thought that comes to mind. However, they need to be refreshed manually or on a schedule, due to which real-time data is not available. To address these issues, Azure SQL Server offers a great feature known as Indexed View. An Indexed View is a physical representation of a view stored in the database with a unique clustered index. Changes to the underlying tables automatically update the indexed view to keep it in sync. It uses a clustered index that organizes the data in the view based on the order of the index keys. The indexed view is ideal for scenarios where we need real-time data, and our query involves complex multi-table joins. It is also suitable for our use case where existing data is rarely updated but queried often, and we have range-based queries and want to do ordered retrieval. There are some things to consider before deciding whether you want to go for indexed views. Indexed views cannot have non-deterministic functions. A non-deterministic function is a function that does not always return the same result for the same input, even when executed with identical arguments and under the same database conditions. Also, an indexed view is an actual structure that requires storage, similar to a Materialized View. The syntax for creating an Indexed View is similar to the View creation query above. However, we cannot have non-deterministic functions while creating an indexed view. The line DATEADD(DAY, -(DATEPART(WEEKDAY, date) - 1), date) AS week_start in the view query above depends on the session-specific SET DATEFIRST setting, which determines the first day of the week. This is considered non-deterministic as it will produce different results for different conditions. Keeping the above things in mind, we can proceed to eliminate the non-deterministic computation by making the column deterministic. We add a week_start column to the underlying table and precompute and fill the week_start value in the table for daily data pulls. So, the rows with dates D1 through D7 belong to W1, D8 to D14 belong to W2, and so on. Now, we can proceed to create an indexed view with the SQL below. MS SQL ALTER TABLE daily_user_metrics ADD week_start DATE;-- Populate this column with first day of the week going forward CREATE VIEW dbo.weekly_user_metric_aggregations_view WITH SCHEMABINDING AS SELECT user_id, week_start, SUM(clicks) AS total_clicks, SUM(visits) AS total_visits, SUM(bookings) AS total_bookings, SUM(reviews) AS total_reviews, SUM(cancellations) AS total_cancellations, COUNT_BIG(*) AS row_count --SQL Server requires COUNT_BIG(*) in indexed views to handle scenarios where the count exceeds the range of an INT data type. FROM dbo.daily_user_metrics GROUP BY user_id, week_start; CREATE UNIQUE CLUSTERED INDEX IX_weekly_user_metric_aggregations_view ON dbo.weekly_user_metric_aggregations_view (user_id, week_start); After this indexed view is created, we can query it as follows: MS SQL SELECT week_start, SUM(total_clicks) AS total_clicks, SUM(total_visits) AS total_visits, SUM(total_bookings) AS total_bookings, SUM(total_reviews) AS total_reviews, SUM(total_cancellations) AS total_cancellations, FROM weekly_user_metric_aggregations_view mv INNER JOIN users u on mv.user_id = u.id WHERE u.age BETWEEN 30 and 40 and u.city ='New York' and m.date_created BETWEEN :startDate and :endDate GROUP BY week_created ORDER BY week_created DESC Conclusion An indexed view will have a significantly faster query time than a basic view or querying the table directly for large datasets where data has grown up to a million rows or more. The end user will have a low latency experience, and queries to the database will be optimized. Since we aggregated a week's worth of data in 1 row, we have cut the aggregation time taken to 1/7th.
The system design of an audio streaming app is unique in how it deals with idiosyncratic business needs. Typically, audio streaming requires a large amount of data to be transferred within the limited bandwidth of the network communication channel. A successful audio streaming service must handle millions of active users and thousands of content providers from various geographical locations. Different devices and platforms (smartphone, desktop, smart speaker) may support different audio formats such as MP3, FLAC, ALAC, and so on. In this blog post, we will explore the nuances of designing such a complex system covering functional and non-functional requirements. Functional Requirements The functional requirements will cover features required for the content creators and audio users or listeners. Content management. The content creator can upload, delete, or update audio with any format. Each audio should have metadata such as title, author, description, category, and metadata tags.Notification. The content creator receives notifications on the success or failure of the audio uploaded. The audio listener or user receives notifications on the successful upload of audio.Seamless streaming. Users can play, pause, rewind, and download audio of their choice of format.Subscribe. Users should be able to subscribe to the audio of their choice to receive notifications on new or updated content.User login. Content creators must be authenticated and authorized to access the system. Users can register with the system to set up their profiles. Search. Users should be able to search audio based on certain attributes or metadata. Note: Live streaming and payment services are out of this article's scope. Sequence Diagram of the Functional Use Case Architectural Design Let’s define service components to support the functional requirements. The first functional requirement is to upload audio using any format such as MP3, FLAC, ALAC, etc. These audio files can hold an impressive amount of data. The audio codec plays a crucial role in efficiently storing, transmitting, and playing this data. There are mainly two types of codecs: Lossless codec – compress audio without losing any data and can be fully restored without losing quality.Lossy codec – removes some data which helps in reducing size significantly. This could compromise sound quality. The content provider typically works with lossless formats for recording and editing. This ensures no loss of quality as they operate the audio, apply effects, and master the final product. In simple terms, audio mastering is the final step in the audio production process. Once the final audio has been mastered, it is converted to a lossy format for general distribution. With a lossy format, the size gets reduced to a greater extent, making it easier and faster to stream and download. The compressed audio needs to be transmitted to the listener device with the supported network bandwidth. The bandwidth could vary dynamically, with the network load changing based on the number of users getting connected or the user moving from one network zone to another while listening to the audio. To support this changing network bandwidth, the codec could use the "adaptive bit rate" mechanism. Adaptive bitrate streaming detects users' bandwidth in real time, adjusting the stream accordingly. It can dynamically switch the bit rate streaming based on the current bandwidth of the user. This results in little buffering, faster start times, and a good experience for both high-end and low-end connections. The encoder encodes the single source of the audio file at multiple-bit rates. These streams of bytes are packaged and stored in the object store to be available to the listener. Once the audio is successfully uploaded, the Notification service sends a notification to the content provider. The content creator additionally provides metadata while requesting to upload the audio, which can be directly saved to a NoSQL DB via Audio Data service. This data will be indexed to offer better searching capability to the audio listener. The audio system needs an authentication and authorization service to handle new user registration, log-in using the user credential, and authorization based on roles associated with the users (listeners and content providers). API Gateway service that can centrally manage authentication and authorization. This service can be leveraged to perform the request-routing, and orchestrating process flow from front-end to back-end service. The audio user (listener) could search for audio of interest, which will be forwarded to the Audio Data service to return the location of the relevant audio(s), pulling info from the audio metadata store. The user clicks on the link, and it will return the audio bytes packaged and stored by the Package service. The User Profile service manages user preferences, followers, and followings. The above diagram depicts the basic "upload audio" process flow triggered by the content provider and the "listen audio" process flow triggered by the audio user/listener. The pipeline and filtering design pattern with message queuing is being used to support the data flow between services to scale the process efficiently with parallelism and fault tolerance. Now, let’s move to non-functional requirements. Non-Functional Requirements Scalability. The system should support thousands of concurrent users and be able to grow and shrink.Fault tolerance. The system should be fault-tolerant, usually with redundant data, with low downtime.Performance. The system should have low latency and high responsiveness during content playback.Security. The system must be safe from unauthorized access or harmful attacks such as DDoS. Scalability The audio system should be able to support thousands of active content creators. To manage the load, the design includes running multiple instances of API Gateway service, App Web service, and Audio Data service fronted by load balancers. However, this will be not scalable with the increase in number of content creators. The audio files are usually large, which will use high network and computing power while traversing through multiple service components. To optimize system resource usage, a signed URL (also referred to as a pre-signed URL) can be used to provide time-limited access to the object store to upload audio files directly. This eliminates the need to route traffic via API Gateway and API Web service. The signed URLs can be configured with granular permissions and expiration rules for better security. This covers the scalability requirement for uploading the audio files by content providers. Millions of search requests from the audio listeners could hit the system, causing the Audio Data service to be overloaded. To scale the system supporting this massive search operation, the Content Query Responsibility Segregation (CQRS) design pattern can be used. The read and write operations from/to datastore can be managed independently, supporting different latency, scalability, and security requirements. Fault Tolerance There are various dimensions to the fault tolerance design. A few of them are already included in the design. Use of multiple instances of service for scalability and fault toleranceMessage queuing with pipeline processing to support fault tolerance at the data flow levelSeparation of transcoder service and packaging serviceMultiple DB instances with replicaAvailability Zones aligning to geographical regions such as the U.S. East, U.S. West, and Asia Pacific for the deployment of the entire system supporting a specific region and userbase. Performance A content delivery network (CDN) is a distributed network of servers that are grouped together to deliver content, such as audio and video files, faster and more efficiently. It caches the content at the edge server to provide better performance with low latency. Security CDN also improves security by providing DDoS mitigation and a few other security measurements. The Package service will distribute the audio files to CDNs to be cached at the edge servers. The Audio Data service will update the location of the CDN to its metadata that will be routed to the Search service for querying by users. The above diagram depicts the high-level component architecture of a typical audio system. Conclusion At the heart of a good audio system are scalability, performance, and fault tolerance to provide a good user experience with minimal distortion, low latency, and reliability.
The year 2025 is the year of AI agents. For the purposes of this article, an AI agent is a system that can leverage AI to achieve a goal by following a series of steps, possibly reasoning on its results and making corrections. In practice, the steps that an agent follows can constitute a graph. We will build a reactive agent (meaning that it reacts to a stimulus, in our case, the input from a user) to help people find their perfect vacation. Our agent will find the best city in the specified country, considering the food, sea, and activity specified by the user. The agent will look like this: In the first phase, it will collect information in parallel, ranking the cities by a single characteristic. The last step will use this information to choose the best city. You could use a search engine to collect information, but we will use ChatGPT for all the steps, though we will use different models. You could write all the code by hand or use some library to help you simplify the code a bit. Today, we will use a new feature that I added to Fibry, my Actor System, to implement the graph and control the parallelism in great detail. Fibry is a simple and small Actor System that provides an easy way to leverage actors to simplify multi-threading code and does not have any dependency. Fibry also implements a Finite State Machine, so I decided to extend it to make it easier to write agents in Java. My inspiration has been LangGraph. As Fibry is about multi-threading, the new features allow plenty of flexibility in deciding the level of parallelism while keeping everything as simple as possible. You should use Fibry 3.0.2, for example: Plain Text compile group: 'eu.lucaventuri', name: 'fibry', version: '3.0.2' Defining the Prompts The first step is defining the prompts that we need for the LLM: Java public static class AiAgentVacations { private static final String promptFood = "You are a foodie from {country}. Please tell me the top 10 cities for food in {country}."; private static final String promptActivity = "You are from {country}, and know it inside out. Please tell me the top 10 cities in {country} where I can {goal}"; private static final String promptSea = "You are an expert traveler, and you {country} inside out. Please tell me the top 10 cities for sea vacations in {country}."; private static final String promptChoice = """ You enjoy traveling, eating good food and staying at the sea, but you also want to {activity}. Please analyze the following suggestions from your friends for a vacation in {country} and choose the best city to visit, offering the best mix of food and sea and where you can {activity}. Food suggestions: {food}. Activity suggestions: {activity}. Sea suggestions: {sea}. """; } Defining the States Normally, you would define four states, one for each step. However, since branching out and back is quite common, I added a feature to handle this with only a single state. As a result, we need only two states: CITIES, where we collect information, and CHOICE, where we choose the city. Plain Text enum VacationStates { CITIES, CHOICE } Defining the Context The different steps of the agent will collect information that needs to be stored somewhere; let’s call it context. Ideally, you would want every step to be independent and know as little as possible of the other, but achieving this in a simple way, with a low amount of code while keeping as much type safety as possible and maintaining thread safety, is not exactly straightforward. As a result, I choose to force the context to be a record, providing some functionality to update the values of the record (using reflection underneath) while we wait for JEP 468 (Derived Record Creation) to be implemented. Java public record VacationContext(String country, String goal, String food, String activity, String sea, String proposal) { public static VacationContext from(String country, String goal) { return new VacationContext(country, goal, null, null, null, null); } } Defining the Nodes Now, we can define the logic of the agent. We will allow the user to use two different LLM models, for example, a “normal” LLM for the search and a “reasoning” one for the choice step. This is where things become a bit trickier, as it is quite dense: Java AgentNode<VacationStates, VacationContext> nodeFood = state -> state.setAttribute("food", modelSearch.call("user", replaceField(promptFood, state.data(), "country"))); AgentNode<VacationStates, VacationContext> nodeActivity = state -> state.setAttribute("activity", modelSearch.call("user", replaceField(promptActivity, state.data(), "country"))); AgentNode<VacationStates, VacationContext> nodeSea = state -> state.setAttribute("sea", modelSearch.call("user", replaceField(promptSea, state.data(), "country"))); AgentNode<VacationStates, VacationContext> nodeChoice = state -> { var prompt = replaceAllFields(promptChoice, state.data()); System.out.println("***** CHOICE PROMPT: " + prompt); return state.setAttribute("proposal", modelThink.call("user", prompt)); }; As you might have guessed, modelSearch is the model used for search (e.g., ChatGPT 4o), and modelThink could be a “reasoning model” (e.g., ChatGPT o1). Fibry provides a simple LLM interface and a simple implementation for ChatGPT, exposed by the class ChatGpt. Please note that calling ChatPGT API requires an API key that you need to define using the “-DOPENAI_API_KEY=xxxx” JVM parameter. Different and more advanced use cases will require custom implementations or the usage of a library. There is also a small issue related to the philosophy of Fibry, as Fibry is meant not to have any dependencies, and this gets tricky with JSON. As a result, now Fibry can operate in two ways: If Jackson is detected, Fibry will use it with reflection to parse JSON.If Jackson is not detected, a very simple custom parser (that seems to work with ChatGPT output) is used. This is recommended only for quick tests, not for production.Alternatively, you can provide your own JSON processor implementation and call JsonUtils.setProcessor(), possibly checking JacksonProcessor for inspiration. The replaceField() and replaceAllFields() methods are defined by RecordUtils and are just convenience methods to replace text in the prompt, so that we can provide our data to the LLM.The setAttribute() function is used to set the value of an attribute in the state without you having to manually recreate the record or define a list of “withers” methods. There are other methods that you might use, like mergeAttribute(), addToList(), addToSet(), and addToMap(). Building the Agent Now that we have the logic, we need to describe the graph of dependencies between states and specify the parallelism we want to achieve. If you imagine a big multi-agent system in production, being able to express the parallelism required to maximize performance without exhausting resources, hitting rate limiters, or exceeding the parallelism allowed by external systems is a critical feature. This is where Fibry can help, making everything explicit but relatively easy to set up. Let’s start creating the agent builder: Plain Text var builder = AiAgent.<VacationStates, VacationContext>builder(true); The parameter autoGuards is used to put automatic guards on the states, which means that they are executed with an AND logic, and a state is executed only after all the incoming states have been processed. If the parameter is false, the state is called once for each incoming state. In the previous example, if the intention is to execute D once after A and once after C, then autoGuards should be false, while if you want it to be called only once after both have been executed, then autoGuards should be true. But let’s continue with the vacation agent. Plain Text builder.addState(VacationStates.CHOICE, null, 1, nodeChoice, null); Let’s start with the method addState(). It is used to specify that a certain state should be followed by another state and execute a certain logic. In addition, you can specify the parallelism (more on that soon) and the guards. In this case: The state is CHOICEThere is no default following state (e.g., this is a final state)The parallelism is 1There is no guard The next state is just a default because the node has the possibility to overwrite the next state, which means that the graph can dynamically change at runtime, and in particular, it can perform cycles, for example, if some steps need to be repeated to collect more or better information. This is an advanced use case. An unexpected concept might be the parallelism. This has no consequences in a single run of the agent, but it is meaningful in production at scale. In Fibry, every node is backed by an actor, which, from a practical point of view, is a thread with a list of messages to process. Every message is an execution step. So parallelism is the number of messages that can be executed at a single time. In practice: parallelism == 1 means there is only one thread managing the step, so only one execution at a time.parallelism > 1 means that there is a thread pool backing the actor, with the number of threads specified by the user. By default, it uses virtual threads.parallelism == 0 means that every message creates a new actor backed by a virtual thread, so the parallelism can be as high as necessary. Every step can be configured configured independently, which should allow you to configure performance and resource usage quite well. Please consider that if parallelism != 1, you might have multi-threading, as the thread confinement typically associated with actors is lost. This was a lot to digest. If it is clear, you can check state compression. State Compression As said earlier, it is quite common to have a few states that are related to each other, they need to be performed in parallel and join before moving to a common state. In this case, you do not need to define multiple states, but you can use only one: Plain Text builder.addStateParallel(VacationStates.CITIES, VacationStates.CHOICE, 1, List.of(nodeFood, nodeActivity, nodeSea), null); In this case, we see that the CITIES state is defined by three nodes, and addStateParallel() takes care of executing them in parallel and waits for the execution of all of them to be finished. In this case, the parallelism is applied to each node, so in this case, you will get three single-thread actors. Please note that if you do not use autoGuards, this basically allows you to mix OR and AND logic. In case you want to merge some nodes in the same state, but they need to be executed serially (e.g., because they need information generated by the previous node), the addStateSerial() method is also available. AIAgent creation is simple, but there are a few parameters to specify: The initial stateThe final state (which can be null)A flag to execute states in parallel when possible Plain Text var vacationAgent = builder.build(VacationStates.CITIES, null, true); Now we have an agent, and we can use it, calling process: Plain Text vacationsAgent.process(AiAgentVacations.VacationContext.from("Italy", "Dance Salsa and Bachata"), (state, info) -> System.out.println(state + ": " + info)); This version of process() takes two parameters: The initial state, which contains the information required by the agent to perform its actionsAn optional listener, for example, if you want to print the output of each step If you need to start the action and check its return value, later, you can use processAsync(). If you are interested in learning more about the parallelism options, I recommend you check the unit test TestAIAgent. It simulates an agent with nodes that sleep for a while and can help you see the impact of each choice: But I promised you a multi-agent, didn’t I? Extending to Multi-Agents The AIAgent that you just created is an actor, so it runs on its own thread (plus all the threads used by the nodes), and it also implements the Function interface, in case you need it. There is actually nothing special about a multi-agent; just one or more nodes of an agent ask another agent to perform an action. However, you can build a library of agents and combine them in the best way while simplifying the whole system. Let’s imagine that we want to leverage the output of our previous agent and use it to calculate how much that vacation would cost so the user can decide if it is affordable enough. Like a real Travel Agent! This is what we want to build: First, we need prompts to extract the destination and compute the cost. Java private static final String promptDestination = "Read the following text describing a destination for a vacation and extract the destination as a simple city and country, no preamble. Just the city and the country. {proposal}"; private static final String promptCost = "You are an expert travel agent. A customer asked you to estimate the cost of travelling from {startCity}, {startCountry} to {destination}, for {adults} adults and {kids} kids}"; We just need two states, one to research the cities, which is done by the previous agent, and one to calculate the cost. Plain Text enum TravelStates { SEARCH, CALCULATE } We also need a context, that should also hold the proposal from the previous agent. Plain Text public record TravelContext(String startCity, String startCountry, int adults, int kids, String destination, String cost, String proposal) { } Then we can define the agent logic, which requires as a parameter another agent. The first node calls the previous agent to get the proposal. Java var builder = AiAgent.<TravelStates, TravelContext>builder(false); AgentNode<TravelStates, TravelContext> nodeSearch = state -> { var vacationProposal = vacationsAgent.process(AiAgentVacations.VacationContext.from(country, goal), 1, TimeUnit.MINUTES, (st, info) -> System.out.print(debugSubAgentStates ? st + ": " + info : "")); return state.setAttribute("proposal", vacationProposal.proposal()) .setAttribute("destination", model.call(promptDestination.replaceAll("\\{proposal\\}", vacationProposal.proposal()))); }; The second node computes the cost: Plain Text AgentNode<TravelStates, TravelContext> nodeCalculateCost = state -> state.setAttribute("cost", model.call(replaceAllFields(promptCost, state.data()))); Then, we can define the graph and build the agent Java builder.addState(TravelStates.SEARCH, TravelStates.CALCULATE, 1, nodeSearch, null); builder.addState(TravelStates.CALCULATE, null, 1, nodeCalculateCost, null); var agent = builder.build(TravelStates.SEARCH, null, false); Now we can instantiate the two agents (I chose to use ChatGPT 4o and ChatGPT 01-mini) and use them: Java try (var vacationsAgent = AiAgentVacations.buildAgent(ChatGPT.GPT_MODEL_4O, ChatGPT.GPT_MODEL_O1_MINI)) { try (var travelAgent = AiAgentTravelAgency.buildAgent(ChatGPT.GPT_MODEL_4O, vacationsAgent, "Italy", "Dance Salsa and Bachata", true)) { var result = travelAgent.process(new AiAgentTravelAgency.TravelContext("Oslo", "Norway", 2, 2, null, null, null), (state, info) -> System.out.println(state + ": " + info)); System.out.println("*** Proposal: " + result.proposal()); System.out.println("\n\n\n*** Destination: " + result.destination()); System.out.println("\n\n\n*** Cost: " + result.cost()); } } Final Outputs If you wonder what the result is, here is the long output that you can get when stating that what you want to do is to dance Salsa and Bachata: Destination Plain Text Naples, Italy Proposal Plain Text Based on the comprehensive analysis of your friends' suggestions, **Naples** emerges as the ideal city for your vacation in Italy. Here's why Naples stands out as the best choice, offering an exceptional mix of excellent food, beautiful seaside experiences, and a vibrant salsa and bachata dance scene: ### **1. Vibrant Dance Scene** - **Dance Venues:** Naples boasts numerous venues and events dedicated to salsa and bachata, ensuring that you can immerse yourself in lively dance nights regularly. - **Passionate Culture:** The city's passionate and energetic atmosphere enhances the overall dance experience, making it a hotspot for Latin dance enthusiasts. ### **2. Culinary Excellence** - **Authentic Neapolitan Pizza:** As the birthplace of pizza, Naples offers some of the best and most authentic pizzerias in the world. - **Fresh Seafood:** Being a coastal city, Naples provides access to a wide variety of fresh seafood dishes, enhancing your culinary adventures. - **Delicious Pastries:** Don't miss out on local specialties like **sfogliatella**, a renowned Neapolitan pastry that is a must-try for any foodie. ### **3. Stunning Seaside Location** - **Bay of Naples:** Enjoy breathtaking views and activities along the Bay of Naples, including boat tours and picturesque sunsets. - **Proximity to Amalfi Coast:** Naples serves as a gateway to the famous Amalfi Coast, allowing you to explore stunning coastal towns like Amalfi, Positano, and Sorrento with ease. - **Beautiful Beaches:** Relax on the city's beautiful beaches or take short trips to nearby seaside destinations for a perfect blend of relaxation and exploration. ### **4. Cultural Richness** - **Historical Sites:** Explore Naples' rich history through its numerous museums, historic sites, and UNESCO World Heritage landmarks such as the Historic Centre of Naples. - **Vibrant Nightlife:** Beyond dancing, Naples offers a lively nightlife scene with a variety of bars, clubs, and entertainment options to suit all tastes. ### **5. Accessibility and Convenience** - **Transportation Hub:** Naples is well-connected by air, rail, and road, making it easy to travel to other parts of Italy and beyond. - **Accommodation Options:** From luxury hotels to charming boutique accommodations, Naples offers a wide range of lodging options to fit your preferences and budget. ### **Conclusion** Naples perfectly balances a thriving dance scene, exceptional culinary offerings, and beautiful seaside attractions. Its unique blend of culture, history, and vibrant nightlife makes it the best city in Italy to fulfill your desires for travel, good food, and lively dance experiences. Whether you're dancing the night away, savoring authentic pizza by the sea, or exploring nearby coastal gems, Naples promises an unforgettable vacation. ### **Additional Recommendations** - **Day Trips:** Consider visiting nearby attractions such as Pompeii, the Isle of Capri, and the stunning Amalfi Coast to enrich your travel experience. - **Local Experiences:** Engage with locals in dance classes or attend festivals to dive deeper into Naples' vibrant cultural scene. Enjoy your trip to Italy, and may Naples provide you with the perfect blend of everything you're looking for! Cost Plain Text To estimate the cost of traveling from Oslo, Norway, to Naples, Italy, for two adults and two kids, we need to consider several key components of the trip: flights, accommodations, local transportation, food, and activities. Here's a breakdown of potential costs: 1. **Flights**: - Round-trip flights from Oslo to Naples typically range from $100 to $300 per person, depending on the time of booking, the season, and the airline. Budget airlines might offer lower prices, while full-service carriers could be on the higher end. - For a family of four, the cost could range from $400 to $1,200. 2. **Accommodations**: - Hotels in Naples can vary significantly. Expect to pay approximately $70 to $150 per night for a mid-range hotel room that accommodates a family. Vacation rentals might offer more flexibility and potentially lower costs. - For a typical 5-night stay, this would range from $350 to $750. 3. **Local Transportation**: - Public transportation in Naples (buses, metro, trams) is affordable, and daily tickets cost around $4 per person. - Assume about $50 to $100 for the family's local transport for the entire trip, depending on usage. 4. **Food**: - Dining costs are highly variable. A budget for meals might be around $10-$20 per person per meal at casual restaurants, while dining at mid-range restaurants could cost $20-$40 per person. - A family of four could expect to spend around $50 to $100 per day, reaching a total of $250 to $500 for five days. 5. **Activities**: - Entry fees for attractions can vary. Some museums and archaeological sites charge around $10 to $20 per adult, with discounts for children. - Budget around $100 to $200 for family activities and entrance fees. 6. **Miscellaneous**: - Always allow a little extra for souvenirs, snacks, and unexpected expenses. A typical buffer might be $100 to $200. **Estimated Total Cost**: - **Low-end estimate**: $1,250 - **High-end estimate**: $2,950 These are general estimates and actual costs can vary based on when you travel, how far in advance you book, and your personal preferences for accommodation and activities. For the most accurate assessment, consider reaching out to airlines for current flight prices, hotels for room rates, and looking into specific attractions you wish to visit. That was a lot, and this is only the output of the two “reasoning” models! But the result is quite interesting. Naples is on my bucket list, and I am curious to see if the agent is correct! Let’s also check the intermediate results to see how it reached this conclusion, which seems reasonable to me. Intermediate Outputs If you are curious, there are intermediate results. Food Plain Text As a foodie exploring Italy, you're in for a treat, as the country boasts a rich culinary heritage with regional specialties. Here's a list of the top 10 cities in Italy renowned for their food: 1. **Bologna** - Often referred to as the gastronomic heart of Italy, Bologna is famous for its rich Bolognese sauce, tasty mortadella, and fresh tagliatelle. 2. **Naples** - The birthplace of pizza, Naples offers authentic Neapolitan pizza, as well as delicious seafood and pastries like sfogliatella. 3. **Florence** - Known for its Florentine steak, ribollita (a hearty bread and vegetable soup), and delicious wines from the surrounding Tuscany region. 4. **Rome** - Enjoy classic Roman dishes such as carbonara, cacio e pepe, and Roman-style artichokes in the bustling capital city. 5. **Milan** - A city that blends tradition and innovation, Milan offers risotto alla milanese, ossobuco, and an array of high-end dining experiences. 6. **Turin** - Known for its chocolate and coffee culture, as well as traditional dishes like bagna cauda and agnolotti. 7. **Palermo** - Sample the vibrant street food scene with arancini, panelle, and sfincione, as well as fresh local seafood in this Sicilian capital. 8. **Venice** - Famous for its seafood risotto, sarde in saor (sweet and sour sardines), and cicchetti (Venetian tapas) to enjoy with a glass of prosecco. 9. **Parma** - Home to the famous Parmigiano-Reggiano cheese and prosciutto di Parma, it’s a haven for lovers of cured meats and cheeses. 10. **Genoa** - Known for its pesto Genovese, focaccia, and variety of fresh seafood dishes, Genoa offers a unique taste of Ligurian cuisine. Each of these cities offers a distinct culinary experience influenced by local traditions and ingredients, making them must-visit destinations for any food enthusiast exploring Italy. Sea Plain Text Italy is renowned for its stunning coastline and beautiful seaside cities. Here are ten top cities and regions perfect for a sea vacation: 1. **Amalfi** - Nestled in the famous Amalfi Coast, this city is known for its dramatic cliffs, azure waters, and charming coastal villages. 2. **Positano** - Also on the Amalfi Coast, Positano is famous for its colorful buildings, steep streets, and picturesque pebble beachfronts. 3. **Sorrento** - Offering incredible views of the Bay of Naples, Sorrento serves as a gateway to the Amalfi Coast and provides a relaxing seaside atmosphere. 4. **Capri** - The island of Capri is known for its rugged landscape, upscale hotels, and the famous Blue Grotto, a spectacular sea cave. 5. **Portofino** - This quaint fishing village on the Italian Riviera is known for its picturesque harbor, pastel-colored houses, and luxurious coastal surroundings. 6. **Cinque Terre** - Comprising five stunning villages along the Ligurian coast, Cinque Terre is a UNESCO World Heritage site known for its dramatic seaside and hiking trails. 7. **Taormina** - Situated on a hill on the east coast of Sicily, Taormina offers sweeping views of the Ionian Sea and beautiful beaches like Isola Bella. 8. **Rimini** - Located on the Adriatic coast, Rimini is known for its long sandy beaches and vibrant nightlife, making it a favorite for beach-goers and party enthusiasts. 9. **Alghero** - A city on the northwest coast of Sardinia, Alghero is famous for its medieval architecture, stunning beaches, and Catalan culture. 10. **Lerici** - Near the Ligurian Sea, Lerici is part of the stunning Gulf of Poets and is known for its beautiful bay, historic castle, and crystal-clear waters. Each of these destinations offers a unique blend of beautiful beaches, cultural sites, and local cuisine, making Italy a fantastic choice for a sea vacation. Activity Plain Text Italy has a vibrant dance scene with many cities offering great opportunities to enjoy salsa and bachata. Here are ten cities where you can indulge in these lively dance styles: 1. **Rome** - The capital city has a bustling dance scene with numerous salsa clubs and events happening regularly. 2. **Milan** - Known for its nightlife, Milan offers various dance clubs and events catering to salsa and bachata enthusiasts. 3. **Florence** - A cultural hub, Florence has several dance studios and clubs where you can enjoy Latin dances. 4. **Naples** - Known for its passionate culture, Naples offers several venues and events for salsa and bachata lovers. 5. **Turin** - This northern city has a growing salsa community with events and social dances. 6. **Bologna** - Known for its lively student population, Bologna has a number of dance clubs and events for salsa and bachata. 7. **Venice** - While famous for its romantic canals, Venice also hosts various dance events throughout the year. 8. **Palermo** - In Sicily, Palermo has a vibrant Latin dance scene reflecting the island's festive culture. 9. **Verona** - Known for its romantic setting, Verona has several dance studios and clubs for salsa and bachata. 10. **Bari** - This coastal city in the south offers dance festivals and clubs perfect for salsa and bachata enthusiasts. These cities offer a mix of cultural experiences and lively dance floors, ensuring you can enjoy salsa and bachata across Italy. Interestingly enough, Naples does not top any of the lists, though the first four cities in the sea list are all close to Naples. Licensing Details Before closing the article, just two words on the license of Fibry. Fibry is no longer distributed as a pure MIT license. The main difference now is that if you want to build a system to generate code at scale for third parties (like a software engineer agent), you need a commercial license. Also, it is forbidden to include it in any datasets to train systems to generate code (e.g., ChatGPT should not be trained on the source code of Fibry). Anything else, you are good to go. I can provide commercial support and develop features on demand. Conclusion I hope you had fun and could get an idea of how to use Fibry to write AI agents. If you think that a multi-agent system needs to be distributed and run on multiple nodes, Fibry has got you covered! While we’ll save the details for another article, it’s worth noting that setting up Fibry actors in a distributed system is straightforward, and your agents are already actors: when you call process() or processAsync(), a message is sent to the underlying actor. In Fibry, sending and receiving messages over the network is abstracted away, so you don’t even need to modify your agent logic to enable distribution. This makes Fibry uniquely simple for scaling across nodes without rewriting core logic. Happy coding!
After working on a new online Spring Data Neo4j course, I learned a couple more things about updating an entity. The course required a different set of scenarios than outlined in my previous SDN update blog post, so I wanted to cover those scenarios, as well. Spring save() Method First up is the out-of-the-box save() method that is provided by Spring as a default. This method takes an input of the entity object you want to save to the database. Here is an example of what this looks like with a popular movie data set as the model. MovieDomain Class Java @Node public class Movie { @Id private String movieId; private String title; private String plot; private String poster; private String url; private String released; private Long imdbVotes; @Relationship(value = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List<Role> actors; } MovieController Class Java @RestController @RequestMapping("/movies") public class MovieController { //inject repository + constructor @PostMapping("/save") Movie save(@RequestBody Movie movie) { return movieRepo.save(movie); } } This works well to save a new movie, as you can provide a subset of properties or all properties, as long as the id is present, and anything you don't provide will be set to null. However, the issue arises when you need to update an existing entity because it means any attributes you do not provide will be overwritten to null. To better understand how this works, let's see it in action by saving a movie, and then trying to update it. Movie Request Object JSON { "movieId": "9876", "title": "MyMovie" } Save Movie Object Shell % http ":8080/movies/save" @src/main/resources/movie.json { "actors": null, "imdbId": null, "imdbVotes": null, "movieId": "9876", "plot": null, "poster": null, "title": "MyMovie", "url": null, "year": null } Now, let's use the same method to try to update the movie with a year property. Updated Movie Object JSON { "movieId": "9876", "year": 2018 } Save Updated Movie Object Shell % http ":8080/movies/save" @src/main/resources/movieUpdated.json { "actors": null, "imdbId": null, "imdbVotes": null, "movieId": "9876", "plot": null, "poster": null, "title": null, "url": null, "year": 2018 } In the output above, you can see that the title property is null, and the year property is populated. This is because the title is not specified in the updated JSON object, so it is overwritten to null. This may not be too big of an effort if you have only a few attributes in your domain class, although I would still find it frustrating to include all properties for only updating one or two properties. In that case, I would need to resort to the POJO method outlined in my previous SDN update blog post where you pull the database entity, set each property, and then save the entity. The added business logic seems like an unnecessary maintenance headache. What if you wanted to preserve what was already there without providing all the properties defined in the domain class to the request? In these scenarios, there are a couple of other options at your disposal, though neither allows dynamic updates to random properties per request. Patch Year The first option is that you don't have to set all properties if you use a PATCH method and only set the values you want to update. Here is an example. MovieController Class Java @RestController @RequestMapping("/movies") public class MovieController { //inject repository + constructor @PatchMapping("/patchYear") Movie patchYear(@RequestBody Movie moviePatch) { Movie existingMovie = movieRepository.findById(moviePatch.getMovieId()).get(); existingMovie.setYear(moviePatch.getYear()); return movieRepository.save(existingMovie); } } PatchYear Request Object JSON { "movieId": "9876", "year": 2024 } Patch Movie Year Shell % http PATCH ":8080/movies/patchYear" @src/main/resources/moviePatch.json { "budget": null, "countries": null, "imdbId": null, "imdbRating": null, "imdbVotes": null, "languages": null, "movieId": "9876", "plot": null, "poster": null, "released": null, "revenue": null, "runtime": null, "title": "MyMovie", "tmdbId": null, "url": null, "year": 2024 } This allows you to set specific values without overwriting other property values to null. You also don't need to set all the values in the movie object programmatically. If you modified the initial save() method to just include the setYear() line, it would still overwrite other values. This approach prevents that, although you still have to call setProperty() for each field you want to update. Note: For this approach to work, your domain entity must be a class (not a record) because records are immutable, which means you cannot change (or set/update) fields on the entity instance. For immutable objects, you have to create a new instance of the object and copy property values to the new object before saving. You can avoid setting each property on an object and retain existing values with a couple of options covered next. Custom Cypher One of the more flexible options is to use custom Cypher. For this, you would write a Cypher statement that sets the new values to the properties you want to update. You can even add/set properties that do not exist on the application's domain class. The negative is that you would need to make changes to the application (Cypher statement) if you wanted to update different properties, so it is not fully dynamic. The example below uses the same movie domain but adds a Cypher statement and method to the repository interface to update the year property without overwriting the title. MovieRepository Interface Java interface MovieRepository extends Neo4jRepository<Movie, String> { @Query("MATCH (m:Movie {movieId: $movieId}) " + "SET m.year = toInteger($year) " + "RETURN m;") Movie updateYear(String movieId, Long year); } MovieController Class Java @RestController @RequestMapping("/movies") public class MovieController { //inject repository + constructor @PatchMapping("/updateYear") Movie patchYear(@RequestParam String movieId, @RequestParam Long year) { return movieRepository.updateYear(movieId, year); } } Then, the following request calls the method and updates the movie's year property. Update Movie Year Shell % http PATCH ":8080/movies/updateYear?movieId=9876&year=2018" { "actors": [], "imdbId": null, "imdbVotes": null, "movieId": "9876", "plot": null, "poster": null, "title": "MyMovie", "url": null, "year": 2018 } It worked! The movie's title remained the same (not overwritten to null), and a value was saved for the year property. This ad hoc Cypher approach could work well when values or property updates occur somewhat frequently, as updating the Cypher statement makes updates flexible. You could also make the incoming property generic (value) and pass in any value (or multiple values) and set whichever properties you'd like by changing the Cypher. While still not completely dynamic, this option is probably the most flexible and dynamic of the list. A custom Cypher approach might work well when you need to update certain properties, but if you have a subset of properties that operate together, another option is to create a projection of the domain class. Projections To provide a consistent set of values for updates and leave other properties as-is, projections are probably the nicest option I've found so far. This approach still requires setting consistent properties (like with custom Cypher) but avoids overwriting consistent values by creating a "view" of the larger entity, only working with those values and leaving other field values alone. Note: There are two types of projections — interface and DTO. Interface projections are immutable, which means you cannot update values, but have to create a new object and copy existing values over. DTO objects, then, are more straightforward when dealing with update operations. For this reason, the examples use DTO-based projections. There are two different ways to save a projection — 1. send the projection (a subset of properties) and save it into the full domain entity; 2. send a full domain object but only save the projection fields. Really, the difference is the incoming request object, whether you have a smaller set or larger set and only want to save those values. Let's see how this operates. Projection as Movie The first example sends a projection object (subset of the full domain object's properties) and saves the trimmed object as the full movie entity. We have defined a projection that only includes the movieId and plot properties of a movie. MovieDTOProjection Class Java public class MovieDTOProjection { private String movieId; private String plot; public String getMovieId() { return movieId; } public String getPlot() { return plot; } public void setPlot(String plot) { this.plot = plot; } } MovieController Class Method Java @RestController @RequestMapping("/movies") public class MovieController { //inject repository + constructor //Only updates properties in projection @PatchMapping("/projectionAsMovie") MovieDTOProjection saveProjectionAsMovie(@RequestBody MovieDTOProjection movieDTO) { return neo4jTemplate.save(Movie.class).one(movieDTO); } } ProjectionAsMovie Object (Request Object) JSON { "movieId": "9876", "plot": "Here is the plot." } Update Movie With a Projection Object Shell % http PATCH ":8080/movies/projectionAsMovie" @src/main/resources/projectionAsMovie.json { "movieId": "9876", "plot": "Here is the plot." } Full Database Entity JSON { "plot": "Here is the plot.", "year": 2024, "movieId": "9876", "title": "MyMovie" } The request successfully updated the entity with the new plot value ("Here is the plot.") without overwriting the title or year properties on the existing entity! The method in the controller class takes the projection object input and saves it as a Movie class entity. This approach is helpful when you only want to send certain properties over the wire and not the full object. You can set any fields you wish to update in the projection and only send those values in the request. Movie Entity as Projection This type of projection only saves a defined subset of properties out of a potentially larger or variable object. The save will only update the fields included in the projection and ignore anything else that might be in the request object. The example below uses the same MovieDTOProjection class as the above example. Then, we only need a new method in the controller to save the projection. MovieController Class Method Java @RestController @RequestMapping("/movies") public class MovieController { //inject repository + constructor //Only updates properties in projection (ignores other values) @PatchMapping("/movieAsProjection") MovieDTOProjection saveMovieAsProjection(@RequestBody Movie movie) { return neo4jTemplate.saveAs(movie, MovieDTOProjection.class); } } MovieAsProjection Object JSON { "movieId": "9876", "title": "TestTitle", "plot": "Some plot cliche here.", "year": 2025 } Send Movie Object (Only Save Projection Values) Shell % http PATCH ":8080/movies/movieAsProjection" @src/main/resources/movieAsProjection.json { "movieId": "9876", "plot": "Some plot cliche here." } Full Database Entity JSON { "plot": "Some plot cliche here.", "year": 2024, "movieId": "9876", "title": "MyMovie" } This also worked! The controller method accepts a Movie request object as input and saves it as the projection entity, which only retains the subset of values defined in the projection and ignores the rest. This approach would be helpful if you have request objects with varying fields of a Movie entity, but only want to update a certain subset each time. In terms of the incoming request object, this option seems to be the most flexible in allowing you to provide all kinds of fields as input, and it will only save the relevant ones defined in the projection. Wrapping Up! In this post, we delved a bit deeper into updating entities using Spring Data and Neo4j. A previous blog post on the topic outlined some examples for that use case at the time (microservices), but I learned about a few more options after working on the GraphAcademy Spring Data Neo4j course. These options included custom Cypher statements and projections. There are still other pieces of the puzzle to explore and integrate, such as optimistic locking (with the @Version field) and custom repository implementations for extending repositories (e.g., to combine Neo4jRepository<>, Neo4jTemplate, and/or Neo4jClient levels of abstraction), but we will save those for a future blog post. Happy coding! Resources Code: sdn-update-entity-round2Blog post: previous SDN update articleGraphacademy course (free, online, self-paced): Spring Data Neo4jDocumentation: SDN projection persistenceDocumentation: SDN custom repository implementations
In this blog, we will explore what chat completion models can and cannot do and then see how Assistants API addresses those limitations. We will also focus on threads and messages — how to create them, list them, retrieve them, modify them, and delete them. Additionally, we will add some Python code snippets and show possible outputs based on the script language. Limitations of Chat Completion Models No Memory Chat completion models do not have a memory concept. For example, if you ask: “What’s the capital of Japan?” The model might say: “The capital of Japan is Tokyo.” But when you ask again: “Tell me something about that city.” It often responds with: “I’m sorry but you didn’t specify which city you are referring to.” It does not understand what was discussed previously. That’s the main issue: there is no memory concept in chat completions. Poor at Computational Tasks Chat completion models are really bad at direct computational tasks. For instance, if you want to reverse the string “openaichatgpt,” it may generate the wrong output, like inserting extra characters or missing some letters. No Direct File Handling In chat completions, there is no way to process text files or Word documents directly. You have to convert those files to text, do chunking (divide documents into smaller chunks), create embeddings, and do vector searches yourself. Only then do you pass some relevant text chunks to the model as context. Synchronous Only Chat completion models are not asynchronous. You must ask a question and wait for it to finish. You cannot do something else while it’s processing without extra workarounds. Capabilities of the Assistants API Context Support With Threads In Assistants API, you can create a thread for each user. A thread is like a chat container where you can add many messages. It persists the conversation, so when the user logs in again, you can pass the same thread ID to retrieve what was discussed previously. This is very helpful. Code Interpreter There is also a code interpreter. Whenever you ask for some computational task, it runs Python code. It then uses that answer to expand or explain. This makes it very helpful for reversing strings, finding dates, or any Python-based operations. Retrieval With Files The Assistants API has retrieval support, letting you upload files and ask questions based on those files. The system handles the vector search process and then uses relevant chunks as context. You can upload up to 20 files in Assistants as context. This is very helpful for referencing company documents, reports, or data sets. Function Calling Function calling allows the model to tell you what function to call and what arguments to pass, so that you can get external data (like weather or sales from your own database). It does not call the function automatically; it indicates which function to call and with what parameters, and then you handle that externally. Asynchronous Workflows The Assistants API is asynchronous. You can run a request, and you don’t have to wait for it immediately. You can keep checking if it’s done after a few seconds. This is very helpful if you have multiple tasks or want to do other things in parallel. Focusing on Threads and Messages A thread is essentially a container that holds all messages in a conversation. OpenAI recommends creating one thread per user as soon as they start using your product. This thread can store any number of messages, so you do not have to manually manage the context window. Unlimited messages. You can add as many user queries and assistant responses as you want.Automatic context handling. The system uses truncation if the conversation grows beyond token limits.Metadata storage. You can store additional data in the thread’s metadata (for example, user feedback or premium status). Below are code snippets to demonstrate how to create, retrieve, modify, and delete threads. 1. Creating an Assistant First, you can create an assistant with instructions and tools. For example: Python from openai import OpenAI client = OpenAI() file_input = client.files.create(file=open("Location/to/the/path", "rb"), purpose = "assistants") file_input.model_dump() Python assistant = client.beta.assistants.create( name="data_science_tutor", instructions="This assistant is a data science tutor.", tools=[{"type":"code_interpreter", {"type":"retrieval"}], model="gpt-4-1106-preview", file_ids=[file_input.id] ) print(assistant.model_dump()) 2. Creating Threads A thread is like a container that holds the conversation. We can create one thread per user. Python thread = client.beta.threads.create() print(thread.model_dump()) id – a unique identifier that starts with thr-object – always "thread"metadata – an empty dictionary by default Why Create Separate Threads? OpenAI recommends creating one thread per user as soon as they start using your product. This structure ensures that the conversation context remains isolated for each user. 3. Retrieving a Thread Python retrieved_thread = client.beta.threads.retrieve(thread_id=thread.id) print(retrieved_thread.model_dump()) This returns a JSON object similar to what you get when you create a thread, including the id, object, and metadata fields. 4. Modifying a Thread You can update the thread’s metadata to store important flags or notes relevant to your application. For instance, you might track if the user is premium or if the conversation has been reviewed by a manager. Python updated_thread = client.beta.threads.update( thread_id=thread.id, metadata={"modified_today": True, "user_is_premium": True} ) print(updated_thread.model_dump()) modified_today – a custom Boolean to note whether you changed the thread todayuser_is_premium – a Boolean flag for user account tierconversation_topic – a string that labels this thread’s main subject Further Metadata Examples {"language_preference": "English"} – if the user prefers answers in English or another language{"escalated": true} – if the thread needs special attention from a support team{"feedback_rating": 4.5} – if you collect a rating for the conversation 5. Deleting a Thread When you no longer need a thread, or if a user deletes their account, you can remove the entire conversation container: Python delete_response = client.beta.threads.delete(thread_id=thread.id) print(delete_response.model_dump()) Once deleted, you can no longer retrieve this thread or any messages it contained. Working With Messages Previously, we focused on threads — the containers that hold conversations in the Assistants API. Now, let’s explore messages, which are the individual pieces of content (questions, responses, or system notes) you add to a thread. We’ll walk through creating messages, attaching files, listing and retrieving messages, and updating message metadata. We’ll also show Python code snippets illustrating these steps. Messages and Their Role in Threads What Are Messages? Messages are mostly text (like user queries or assistant answers), but they can also include file references. Each thread can have many messages, and every message is stored with an ID, a role (for example, "user" or "assistant"), optional file attachments, and other metadata. Opposite Index Order Unlike chat completions, where the first message in the list is the earliest, here, the first message you see in the array is actually the most recent. So, index 0 corresponds to the newest message in the thread. Annotations and File Attachments Messages can include annotations, for instance, if a retrieval step references certain files. When using a code interpreter, any new files generated may also appear as part of the message annotations. Create a Message in a Thread Messages are added to a thread. Each message can be a user message or an assistant message. Messages can also contain file references. Before adding messages, we need a thread. If you do not already have one: Python # Create a new thread new_thread = client.beta.threads.create() print(thread.model_dump()) # Shows the thread's detailspython Python # Create a new message in the thread message = client.beta.threads.messages.create( thread_id=thread.id, role="user", content="ELI5: What is a neural network?", file_ids=[file_input.id] # Passing one or more file IDs ) print(message.model_dump()) Here, you can see: Message ID – unique identifier starting with msgRole – user, indicating this is a user inputFile attachments – the file_ids list includes any referenced filesAnnotations – empty at creation, but can include details like file citations if retrieval is involvedMetadata – a placeholder for storing additional key-value pairs List Messages in a Thread To list messages in a thread, use the list method. The limit parameter determines how many recent messages to retrieve. Now, let’s try to list all the messages: You will see only the most recent messages. For instance, if we’ve added just one message, the output will look like: Python messages_list = client.beta.threads.messages.list( thread_id=thread.id, limit=5 ) for msg in messages_list.data: print(msg.id, msg.content) If there are multiple messages, the system works like a linked list: The first ID points to the newest message.The last ID points to the earliest message. Retrieve a Specific Message Python retrieved_msg = client.beta.threads.messages.retrieve( thread_id=new_thread.id, message_id=message.id ) print(retrieved_msg.model_dump()) Retrieve a Message File Now, let’s retrieve a message file: This provides the file’s metadata, including its creation timestamp. Python files_in_msg = client.beta.threads.messages.files.list( thread_id=new_thread.id, message_id=message.id ) print(files_in_msg.model_dump()) Modify a Message Python updated_msg = client.beta.threads.messages.update( thread_id=new_thread.id, message_id=message.id, metadata={"added_note": "Revised content"} ) print(updated_msg.model_dump()) Delete a Message Python deleted_msg = client.beta.threads.messages.delete( thread_id=new_thread.id, message_id=message.id ) print(deleted_msg.model_dump()) We have seen that chat completion models have no memory concept, are bad at computational tasks, cannot process files directly, and are not asynchronous. Meanwhile, Assistants API has context support with threads, code interpreter for computational tasks, retrieval for files, function calling for external data, and it also supports asynchronous usage. In this blog, we focused on how to create, list, retrieve, modify, and delete threads and messages. We also saw how to handle file references within messages. In the next session, we will learn more about runs, which connect threads and assistants to get actual outputs from the model. I hope this is helpful. Thank you for reading! Let’s connect on LinkedIn! Further Reading Where did multi-agent systems come from?Summarising Large Documents with GPT-4oHow does LlamaIndex compare to LangChain in terms of ease of use for beginnersPre-training vs. Fine-tuning [With code implementation]Costs of Hosting Open Source LLMs vs Closed Sourced (OpenAI)Embeddings: The Back Bone of LLMsHow to Use a Fine-Tuned Language Model for Summarization
Elevating Software Delivery Through Pair Programming
February 13, 2025 by
Why and How to Participate in Open-Source Projects in 2025
February 13, 2025
by
CORE
Top 5 GRC Certifications for Cybersecurity Professionals
February 11, 2025 by
Redis as a Primary Database for Complex Applications
February 14, 2025 by
Optimizing Database Performance in Middleware Applications
February 14, 2025 by
February 14, 2025 by
Reactive Programming in React With RxJS
February 14, 2025 by
Redis as a Primary Database for Complex Applications
February 14, 2025 by
Optimizing Database Performance in Middleware Applications
February 14, 2025 by
Reactive Programming in React With RxJS
February 14, 2025 by
Redis as a Primary Database for Complex Applications
February 14, 2025 by
Robust Integration Solutions With Apache Camel and Spring Boot
February 14, 2025 by
Redis as a Primary Database for Complex Applications
February 14, 2025 by
Robust Integration Solutions With Apache Camel and Spring Boot
February 14, 2025 by
February 14, 2025 by
Reactive Programming in React With RxJS
February 14, 2025 by
February 14, 2025 by
Unlocking Local AI: Build RAG Apps Without Cloud or API Keys
February 14, 2025
by
CORE