<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Brizic</title>
    <description>The latest articles on DEV Community by Michael Brizic (@aquacode).</description>
    <link>https://dev.to/aquacode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F924852%2F418f927b-0d39-4372-8727-f4c71a9d4739.jpg</url>
      <title>DEV Community: Michael Brizic</title>
      <link>https://dev.to/aquacode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aquacode"/>
    <language>en</language>
    <item>
      <title>JaVers: Periodic Maintenance of the Audit Tables</title>
      <dc:creator>Michael Brizic</dc:creator>
      <pubDate>Thu, 04 Apr 2024 13:08:04 +0000</pubDate>
      <link>https://dev.to/aquacode/javers-periodic-maintenance-of-the-audit-tables-2oeg</link>
      <guid>https://dev.to/aquacode/javers-periodic-maintenance-of-the-audit-tables-2oeg</guid>
      <description>&lt;p&gt;This article is about using JaVers for versioning your data object's within your Java application and performing maintenance on the JaVers audit tables that are used to track the changes.&lt;/p&gt;

&lt;p&gt;JaVers is meant for storing an audit trail of changes made to an application data model. It's a very nice software product and is very simple to integrate into a Java Spring Boot application. Read here for further.&lt;/p&gt;

&lt;p&gt;One day I discovered that the development and testing environments' databases had grown super huge. It turns out that the JaVers audit tables were contributing the most to the overall size.&lt;/p&gt;

&lt;p&gt;However, I did not find any out-of-the-box feature that allows one to perform periodic maintenance of the audit tables. As far as I know, the audit tables simply grow and grow, along with all the changes to the data model, ad infinitum. I also did not find any documentation on how to do this.&lt;/p&gt;

&lt;p&gt;I opened a GitHub question but it wasn't answered as quickly as I hoped. The development and testing environments' databases were so large that I was suddenly running out of disk space. I thought it might be nice to clean up the audit tables that JaVers uses rather than simply add more disk space since after all, the environments were merely for development and testing.&lt;/p&gt;

&lt;p&gt;After studying the source code and the tables I realized that there are a total of four tables that JaVers creates and uses. I also figured out their relationships to one another and how cleaning up the data would be executed.&lt;/p&gt;

&lt;p&gt;In my particular case, the database is MySQL and runs inside of a Docker container. So my solution includes interacting with the database via the Docker container. &lt;/p&gt;

&lt;p&gt;The first part that I'll share below are the tables and the commands needed to reduce the size of data that they store. It requires that you choose a data for which all data from that point going backwards will be removed. The data provided should be given as yyyy-MM-dd.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9su20derk6dr1nb5po6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9su20derk6dr1nb5po6z.png" alt="Image description" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above would be saved to a file named cleanup-javers.sql. Then, the commands of the SQL file will be executed according to the following convenience script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjr9ccdgz0hea6hkuc8la.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjr9ccdgz0hea6hkuc8la.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For convenience, I also added the above to files and hosted them in my GitHub located here.&lt;/p&gt;

&lt;p&gt;Let me know if some corrections need to be made or enhancements. Thanks!&lt;/p&gt;

</description>
      <category>java</category>
      <category>javers</category>
      <category>mysql</category>
      <category>maintenance</category>
    </item>
    <item>
      <title>Java 17 Upgrade with Spring Boot, Hibernate, MySQL and H2</title>
      <dc:creator>Michael Brizic</dc:creator>
      <pubDate>Sun, 08 Jan 2023 13:15:00 +0000</pubDate>
      <link>https://dev.to/aquacode/java-17-upgrade-with-spring-boot-hibernate-mysql-and-h2-1h5d</link>
      <guid>https://dev.to/aquacode/java-17-upgrade-with-spring-boot-hibernate-mysql-and-h2-1h5d</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;This article considers a web application based on the following software versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Boot version 2.6.8

&lt;ul&gt;
&lt;li&gt;Hibernate &lt;a href="http://5.6.9.FINAL" rel="noopener noreferrer"&gt;5.6.9.FINAL&lt;/a&gt; (as a managed dependency of Spring Boot)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Java 11&lt;/li&gt;

&lt;li&gt;MySQL 5.7 (for server/application use)&lt;/li&gt;

&lt;li&gt;H2 (for integration tests)&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt; To upgrade the web application to Java 17 (the new LTS version).&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Upgrading a Java 11 based Spring Boot web app to 17 was fairly smooth until I opened a PR and the code was built using CI hosted by Bitbucket Pipelines.&lt;/p&gt;

&lt;p&gt;Prior to opening the PR I had built and ran all unit and integration tests locally on my MacBook Pro. I also ran the web app and did some user acceptance testing ensuring the app actually works.&lt;/p&gt;

&lt;p&gt;So what was the problem when building and running the integration tests on the CI machines?&lt;/p&gt;

&lt;p&gt;The Spring Boot JPA was throwing OptimisticLockingFailureExceptions but the cause was the internal Hibernate StaleObjectStateException.&lt;/p&gt;

&lt;p&gt;I was struck by the oddity of this outcome since locally all tests passed. And, I ran the application as well and everything worked just fine. So why were the integration tests failing in the CI build (Bitbucket Pipeline)?&lt;/p&gt;

&lt;h2&gt;
  
  
  Guessing At the Answer
&lt;/h2&gt;

&lt;p&gt;You would be right if you guessed that the problem might be related to the environment. I too realized that my MacBook laptop is not the same as running the build in the CI environment. But I was surprised to learn exactly &lt;em&gt;what&lt;/em&gt; about the different environments was actually the source of the problem.&lt;/p&gt;

&lt;p&gt;I observed that integration tests which were performing inserts and updates to rows in our H2 database (used for integration tests) were indeed failing the &lt;a class="mentioned-user" href="https://dev.to/version"&gt;@version&lt;/a&gt; check performed by Hibernate. But why? I decided to Google and found the below post on a Hibernate forum:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discourse.hibernate.org/t/java-17-version-and-nanoseconds-truncation/6128" rel="noopener noreferrer"&gt;Java 17 @version and nanoseconds truncation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the bug that was opened and fixed by Hibernate&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hibernate.atlassian.net/browse/HHH-15135" rel="noopener noreferrer"&gt;https://hibernate.atlassian.net/browse/HHH-15135&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK. So the forum and bug ticket seemed very promising as to the source of the problem, but why did it work on my machine, and not the CI? Why did the web app work when deployed too?&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Research and Understanding
&lt;/h2&gt;

&lt;p&gt;I thought I had read all the cool new features that were releases as part of Java 17 LTS. I don’t recall reading anything about nanosecond precision! Apparently, the Java team introduced nanosecond precision to their system clock in JDK 15. Of course, Java 17 includes these changes.&lt;/p&gt;

&lt;p&gt;(See below the links to the original bug/ticket, the pull request with source changes, and the email detailing the enhancement)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bugs.openjdk.org/browse/JDK-8242504" rel="noopener noreferrer"&gt;Enhance the system clock to nanosecond precision&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hg.openjdk.java.net/jdk/jdk/rev/f1f88e6fad02" rel="noopener noreferrer"&gt;jdk/jdk: f1f88e6fad02&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mail.openjdk.org/pipermail/hotspot-runtime-dev/2020-April/038975.html" rel="noopener noreferrer"&gt;os::javaTimeSystemUTC to call nanosecond precision OS API, so Clock.systemUTC() can give nanosecond precision UTC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Very interesting, yes?&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Sense of It All
&lt;/h2&gt;

&lt;p&gt;An example entity had the following &lt;a class="mentioned-user" href="https://dev.to/version"&gt;@version&lt;/a&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Version&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columnDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DATETIME(6)"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This entity property indicates that the field named &lt;code&gt;version&lt;/code&gt; is a Java Instant and will be used for versioning database rows. The &lt;a class="mentioned-user" href="https://dev.to/column"&gt;@column&lt;/a&gt; annotation with the &lt;code&gt;columnDefinition&lt;/code&gt; property is going to use DDL to create this table column as a datetime with precision of 6. But didn’t we learn that Java ≥ 15 now uses nanosecond precision for time? I immediately checked docs for MySQL and H2. MySQL datetime columns &lt;em&gt;only&lt;/em&gt; allow a maximum precision of 6. But, H2 allows a maximum of 9! Furthermore, H2 doesn’t have a data type known as datetime, so properties with this type will be coerced into timestamps. It would seem like all this information is beginning to make sense, but still, how did the integration tests pass on my MacBook but not CI?&lt;/p&gt;

&lt;p&gt;The answer to that has to do with Java supporting the time precision that is allowed by the OS libraries it has access to. So, if you are running Java on an Apple Mac, the highest precision will be 6. But running Java on a Linux based machine will support a time precision of nanoseconds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let’s recap:&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Java 15 and onward, uses the precision supported by the OS. I didn’t have any errors on my Apple MacBook Pro because it doesn’t support nanosecond precision. Linux however, &lt;em&gt;DOES&lt;/em&gt; support nanosecond precision.&lt;/li&gt;
&lt;li&gt;MySQL only stores datetimes with microsecond precision.&lt;/li&gt;
&lt;li&gt;H2 coerces datetime types into timestamps and fully supports up to 9 digits of precision i.e. nanoseconds.&lt;/li&gt;
&lt;li&gt;Datetime objects created with Java and the underlying OS with nanosecond precision will be chopped off to microseconds and thus any &lt;a class="mentioned-user" href="https://dev.to/version"&gt;@version&lt;/a&gt; comparisons within Hibernate will result in OptimisticLockExceptions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But ***&lt;strong&gt;&lt;em&gt;WHY&lt;/em&gt;&lt;/strong&gt;*** does it work with MySQL (server/application use) and not H2 (integration tests only on OS that support nanosecond precision)?&lt;/p&gt;

&lt;p&gt;The answer has to do with &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt; each of the databases differs in the handling of the datetime data types with respect to Hibernate. It should be obvious that if the system, and thus Java, support nanoseconds, but the database data column is defined with datetime(6), then rounding must occur. Apparently, Hibernate in the generation of the SQL queries will rely on the specific database implementation for handling the data types. Read here for more details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discourse.hibernate.org/t/explain-why-datetime-types-for-version-get-ole-using-java-15/7085/10" rel="noopener noreferrer"&gt;Explain why datetime types for @Version get OLE using &amp;gt;= Java 15&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;H2 is handling the rounding of nanosecond Instants and what they are compared to within Hibernate for the SQL update query differently than MySQL and thus using a Java Instant with nanosecond precision stored as an &lt;a class="mentioned-user" href="https://dev.to/version"&gt;@version&lt;/a&gt; property with a lower precision of microseconds does not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Make the &lt;code&gt;columnDefintion&lt;/code&gt; datetime precision equal to nanoseconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Version&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columnDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DATETIME(9)"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solves the problem with H2 when running integration tests on JVMs running on OSs that support that level of precision. Furthermore, the highest level of precision allowed on MySQL is microseconds so this setting will result in a datetime with the default precision of 6.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternate Solutions (considered but not feasible)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Upgrade Hibernate&lt;/p&gt;

&lt;p&gt;You will have noticed that the issue of handling precision with Java datetime objects was resolved if you read the link to the Hibernate bug ticket earlier. However, the bug was fixed in version 6. So why not upgrade to the Hibernate library version that contains this bug fix? Well, in Spring Boot 2.6.8, Hibernate 6 is prohibited. This has to do with an even larger change that occurred within Jakarata, the newest Java EE specification. The namespaces were changed and thus running an application on Spring Boot 2.6.8 does not allow using Hibernate 6.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upgrade Spring Boot&lt;/p&gt;

&lt;p&gt;Another approach considered was to update Spring Boot itself in order to get Hibernate 6. This proved unfeasible because I would have to update the web app to Spring Boot 3 and this in itself will be a rather non-simple migration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don’t use H2 for integration tests&lt;/p&gt;

&lt;p&gt;Yet another approach was to NOT use H2 for the integration tests. This too was not ideal (at the time) since the integration test is large and varied. H2 provides speed as it is in-memory (we don’t need to save the data). Our original goal was to avoid starting up separate database processes and extending the time that it would take to run tests as part of our continuous integration pipeline.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since solving this problem I have discovered something known as testcontainers and will be exploring that as an alternative to using H2 in-memory. My work on testcontainers will likely result in another article once I figure it out and get it working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.testcontainers.org/" rel="noopener noreferrer"&gt;https://www.testcontainers.org/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discord</category>
      <category>bot</category>
    </item>
    <item>
      <title>Solving Problems By Guessing</title>
      <dc:creator>Michael Brizic</dc:creator>
      <pubDate>Mon, 12 Sep 2022 10:22:18 +0000</pubDate>
      <link>https://dev.to/aquacode/solving-problems-by-guessing-57jf</link>
      <guid>https://dev.to/aquacode/solving-problems-by-guessing-57jf</guid>
      <description>&lt;p&gt;&lt;em&gt;I think that we shall have to get accustomed to the idea that we must not look upon science as a 'body of knowledge,' but rather as a system of hypotheses; that is to say, as a system of guesses or anticipations which in principle cannot be justified, but with which we work as long as they stand up to tests, and of which we are never justified in saying that we know they are 'true' or 'more or less certain' or even 'probable.'&lt;/em&gt;&lt;br&gt;
-- Karl Popper&lt;/p&gt;

&lt;p&gt;Karl's quote not only applies to explanatory knowledge but is equally as important regarding science that includes the solving of particular problems.&lt;/p&gt;

&lt;p&gt;Software development is definitely a part of that science. Guessing is how I, and many software engineers, find a working solution to the various problems that arise. Indeed, almost anyone trying to solve a problem uses some form of guessing to some extent. There is absolutely nothing wrong with the educated guess. In fact, more often than not, I have no absolute answers at all and am guessing at almost every problem that arises.&lt;/p&gt;

&lt;p&gt;Recently, I have had the pleasure of upgrading a Java web server to compile and run on version 17. The original version upon which it was based is 11. This type of work usually requires a significant amount of time due to the longer feedback loop of having to not only pass compilation, but also all test suites, including running the app.&lt;/p&gt;

&lt;p&gt;I manage to get through the aforementioned gates on my local machine, a MacBook Pro. I open a pull request for the team to review and let the CI pipeline do its thing. It fails. Many of the integration tests are causing the build to fail. There's a problem, and it has to be solved. Am I instantly conjuring up stored knowledge that succinctly solves the problem? No.&lt;/p&gt;

&lt;p&gt;Immediately, the guesswork begins. Questions come to mind in the form of hows and whys. All a very natural part of the process. Much of it in the beginning is in the form of wild finger-pointing, mostly at oneself. Did I forget X? Is something wrong in the way I merged and kicked off the build? Bad parameters? But how did everything work locally? Must be an environmental issue. Is my local environment off? How is my local environment different from the CI pipeline's environment?&lt;/p&gt;

&lt;p&gt;As one reflects, and questions the source of the problem, some questions will appear more likely than others. Selection among the candidates is the logical next step. Which of the thoughts, the questions, the guesses does one pursuit?&lt;/p&gt;

&lt;p&gt;This is exactly how many problems are solved: creative guessing followed by selection and testing. Realize there is a problem, start wondering at how or why it is a problem, select among the guesses as to which one it might be, and then test whether the guess is valid and leads one towards identifying the problem. It is very rare that I draw upon some stored knowledge whereby I'm able to immediately and definitively answer or solve a problem in an instant.&lt;/p&gt;

&lt;p&gt;Of course some guesses are going to be better than others, and have a higher probability at determining the source of the problem and how to solve it. This is essentially the meaning behind the oft-repeated apothegm of take an educated guess. So it's not just that one is guessing but that one is using prior knowledge and experience to inform the guess. One may even be using instincts (but perhaps informed by past experience). Frankly, there are times when one's guess is informed by others. This too, is an educated guess, just not necessarily educated by oneself. Other people, or knowledge sources, don't have 100% of the problem context but can provide insight with some level of surety that perhaps their knowledge and experience will identify the problem source and a path to a solution, having studied or solved something similar in the past.&lt;/p&gt;

&lt;p&gt;Usually wild and ridiculous guessing is not really fruitful. Certainly there are times when spit-balling is useful and necessary such as when one doesn't even know where to begin or has had no prior experience or knowledge within a given problem context. And, random, uneducated guessing has proven itself able to solve problems, since this approach can potentially lead to unorthodox thinking and discovery. Rather, the educated guess, guessing with human creativity, instinct, or intuition, is going to be more efficient and effective. And thus one tries to use all previous knowledge one has acquired including any and all experiences to build a set of potential, testable guesses. Before even trying one of the guesses it's worthwhile to think them through. The so-called thought experiment is useful during the process. One might test the guess inside one's mind before even trying it in reality. Absolutely put the guess to a test in reality if thought experiments don't yield progress due to challenges of the problem context. This must be done eventually anyway. Thought experiments may be helpful in reducing the amount of time one spends selecting from among the set of guesses.&lt;/p&gt;

&lt;p&gt;Testing cannot be overemphasized enough. It is without exception, necessary. For making educated guesses will always remain open to error correction. And this is the portion related to the practice of science that underscores the rarity of it being 100% absolutely correct for all of time. Guessing is the way, but fallibilism in the guesses must be accepted, and tolerated. Even after one has tested one's most cherished guesses, there could be any number of other factors that come along at some point and prove the guesses incomplete, or simply wrong. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;First you guess. Don’t laugh, this is the most important step. Then you compute the consequences. Compare the consequences to experience. If it disagrees with experience, the guess is wrong. In that simple statement is the key to science. It doesn’t matter how beautiful your guess is or how smart you are or what your name is. If it disagrees with experience, it’s wrong. That’s all there is to it.&lt;/em&gt; -- Richard Feynman&lt;/p&gt;

&lt;p&gt;In summary, guessing is an important part of science, notwithstanding, in solving problems. It's the educated guesses that usually yield more efficient and effective success although uneducated guessing sometimes works by revealing fresh ideas. Creative guessing is harder and requires more thinking to narrow the applicability to the problem. And guessing is not the complete process as one must also think with knowledge in order to select among the set of guesses which will likely be correct. Lastly, all guesses must be tested. The testing is sometimes done in one's mind playing out scenarios against outcomes. However, subjecting one's guesses to physical testing is required. In many cases, continual testing must be done to ensure that one's guess hasn't missed some part of the picture, or correct various errors that aren't always apparent initially.&lt;/p&gt;

</description>
      <category>software</category>
      <category>science</category>
      <category>beginners</category>
      <category>thinking</category>
    </item>
  </channel>
</rss>
