• Projects
  • Archive
  • Links
  • Abstractions, Future-Proofing, and a Reasonable Amount Of Effort

    I’ve been looking into Tempest. It’s a new PHP framework that is built for modern PHP: type hinting, property accessors, the works. I’m most excited about its approach to discovery. It closely models what I’ve been building towards in Smolblog’s framework, but with infinitely less manual configuration.

    So this past weekend, I started playing around with seeing if I could get Smolblog’s core domain model working. And… I ran into a speed bump. It wasn’t hard to get around, but I was curious about the reasons behind it. And when I asked about said reasons… well, apparently I wasn’t the first.

    Which brings me to this blog post. This isn’t me trying to die on this hill or even claim some semblance of high ground. I’ve just been thinking about this a lot since the topic was broached and I wanted to get my thoughts in order. But I especially want to emphasize: Nothing in here is meant as an insult to others or a statement on the quality of their code. I have my perspective, and they have theirs, and none of that means we can’t work together.

    That being said, let’s get some context.

    The Umpteenth Rewrite

    I’ve been working on Smolblog for way too long. I started it as a WordPress plugin, but as I learned more about SOLID principles and modern Object-Oriented software, I realized I had bigger ambitions. Especially when it felt like what I wanted to do and how I was thinking about content management was running headlong into some of the decisions WordPress made about how it approached things.

    So, I made a decision that honestly haunts me to this day: the core of Smolblog’s code would be framework-independent. I say “haunts” because while it’s meant the code I’m writing is—theoretically—incredibly versatile, it also means I can’t take advantage of any of the shortcuts that come from using a framework.

    That being said, while development has been slow, it’s also forced me into some design decisions that I’m happy about:

    • I’ve ended up with a distinction between Value and Service objects: Values have state but no logic. Services have logic but no state. It’s helped me stay closer to making “single responsibility” objects.
    • I haven’t had any real input or output with the system, since a user interface is an implementation detail. Which means the only way I know something works is to write a test. So I’ve got a lot of cussing tests.
    • If the code is something that is core to the application, it goes in. If it’s an implementation detail, it gets an interface and I move on.

    That last point was helped a lot by the existence of PSR interfaces. These are recommended standards for pieces of infrastructure that are often found in frameworks. For a concrete example, even though I wrote my own dependency injection container, the core domain model only needs something that implements PSR-11. Which means, if I want to finally stop building my own stuff I can swap out my container for the one in Tempest!

    Except… Tempest doesn’t implement PSR-11.

    The Other Side Of the Coin

    Naturally I asked in the community chat about this, trying to get an idea of the reasoning. Brent, the lead developer/architect/founder? of Tempest pointed me to an older blog post of his, with the note that the “long version” (describing how it pertained to Tempest specifically) still needed to be written1. Between that and some of the ensuing conversation, I gathered a few points:

    • The design of Tempest’s components don’t always match up with the PSR specification.
    • Having interoperability between frameworks doesn’t make sense when code is tied so closely to its framework.
    • The interfaces designed by the PHP FIG can be outdated or only fit for a specific purpose.
    • For PSR-11 specifically, Brent cites two frameworks, Laravel and Symfony, who both implement the Container standard yet add several methods on top of it. Any real use of the class would inevitably be tied to the specific framework.

    I highly encourage you to read Brent’s post, especially as at the end he acknowledges the popular use case of smaller packages being used in multiple frameworks.

    It’s that particular use case that I end up falling into for Smolblog: the core domain model isn’t written as a monolithic application but as a library to be used by an application. There’s so much more that goes into an app than just the core logic; in fact that’s often some of the smallest parts! But in this particular case, I wanted to take the time to get this core logic right… and I had another motivation.

    Fool Me Once

    I didn’t realize until I went to write this paragraph how ingrained the idea of limiting dependencies is to Smolblog. The first essays around the idea of Smolblog came in the wake of poorly-implemented policy changes at Tumblr. The original feature set leaned heavily on POSSE, the IndieWeb idea of owning your own content.

    So it makes sense that I wanted to reflect this idea in code. I didn’t want to be trapped into a particular framework’s idioms and then find out later that it would have been better to pick a different one. Part of this came from writing a different app with a WordPress backend and realizing… I wasn’t getting anything out of WordPress that I couldn’t get elsewhere with less hassle.

    So while I do plan on using Tempest—and I have the highest respect for Brent as someone that’s actually doing the thing he blogged about!—I wanted to push back against this:

    I’m more than happy to ditch “interoperability” — what does that even mean when you’re building a project closely tied to a framework and never intend to change it — and just use a simpler, straight forward, opinionated solution.

    You never intend to change it until you do. The framework is just what you need until it isn’t. One thing that I took away from POODR was the idea of using dependency injection and interfaces to keep your code reusable. And no one with any serious experience with object-oriented programming disagrees, Brent included!

    What I keep coming back to is when I finally understood the reasons for dependency injection:

    I genuinely think part of the reason SOLID never clicked for me at the agency was the simple fact that we were always going to be using WordPress. […] Now that I’m working on a project—a big one!—that doesn’t have that constraint, I’m beginning to see the value even if we were staying with WordPress.

    If you know your project won’t outgrow your framework—perhaps the scope is limited by your contract or ambition—then hack away. Even if you have bigger plans, building a project tightly coupled to a framework is fine if that’s what it takes to build the cussing thing! Am I speaking for myself? Yes.

    But I was always going use WordPress… until I wasn’t.


    1. To be extra clear: as someone who typically writes code five times faster than blog posts, there’s no judgement here! ↩︎

    → 9:56 PM, Aug 4
    Also on Bluesky
  • Introducing Grimoire

    TL;DR: I'm building Deckbox but for Pokémon cards. Headless WordPress app with a Next.js/React frontend. You can browse the catalog now; you can also request a beta invite if you want to try it out. Want to learn more? Read on!


    My first job out of college was for Blackbaud working on their next-generation platform. It was a software-as-a-service built with an API-first design: every action taken by a user, even through the official app, went through the API. During my time there, the primary app went from a Windows application to a Javascript application (something that made my Mac-loving heart happy), and this was possible because the user interface was decoupled from the application logic.

    I think this architecture has stuck with me more than I realized. As headless WordPress took off, I had the chance to learn how to properly build a API-based application. Now all I needed was a problem to solve...

    A problem like the massive amount of Pokémon cards in my collection. I've started selling some of them on TCGplayer, and while they have a decent app, it didn't quite fit my needs. I needed an application I could store my catalog in and quickly check it for cards that have increased in value. It also needed to be able to tag different versions of the same card for when it came time to build a deck.

    I'd worked on something for this before, even wrote a blog post about it, but now it's time to finish the job. To that end, let me introduce Grimoire.

    Yeah, it doesn't look like much. In the interest of finishing, this is a minimally viable product. In this case, lots of Bootstrap. But let me show you what there is!

    The Features

    One one level, Grimoire is just a catalog of Pokémon cards. It uses the TCGplayer API to get the different cards. TCGplayer is already invested in having an extensive catalog of all the Pokémon cards printed, so that is thankfully work I do not have to do. For Grimoire, I wanted to add two things to their catalog:

    Unique, Discoverable IDs

    A Grimoire ID (pkm-evs-49-r in the screenshot) consists of up to 4 parts:

    • The game the card is from. In this case, pkm denotes a Pokémon card. This part is mostly in place for when I inevitably support for Magic the Gathering.
    • The set the card is from. This card is from the Evolving Skies set, so it's abbreviated evs.
    • The card number, including any descriptors and ignoring any total numbers.
    • One last part for any extra modifiers that are not part of the card number. The card in the screenshot is a reverse holographic printing, so its ID has an extra r.

    The idea is that by looking at the card, you can infer the ID as long as you know the patterns. This is the part of the project that's going to take the longest, as there is a major manual process to all this. Most cards can fit in this pattern, but there are always exceptions. There are deck-exclusive variants, league staff variants, and a bunch of other cards that will have to be manually assigned IDs.

    It's okay. It's not like I have a full-time job or anything.

    Identify Alternate Printings

    The card in the screenshot above is a reverse-holographic printing. There's also a normal, non-holographic printing. These cards are the same from a gameplay perspective, but they have different collection values. With Grimoire, alternate printings are all linked together:

    Different card, different price, but same text. The two versions of this card link to each other. This is largely in place so that, in the future, it can be easier to find out which cards you have as you're building a deck. Some desirable cards may have more inexpensive versions. That's why it was important for this feature not just to work within a set, as shown for the Pikachu card, but between different sets.

    One of the headline cards for Evolving Skies was Umbreon VMAX, shown in this screenshot:

    There was also a "secret" version of this card with alternate artwork:

    Very cool! And very expensive. But that wasn't the last time they printed this card. In the Brilliant Stars set, there is a special collection called the Trainer Gallery featuring Pokémon posing with their trainers. And here's the gigantic Umbreon:

    All three of these are different cards with (very!) different prices. But when building a deck, all three are functionally the same.

    Personal Collections

    But I set out to build a personal catalog, not just a list. So once I've logged in, how does that change things?

    At the bottom of each card's page, there is a list of the different collections I've made. I can change the quantity of this card in each of those collections. In this case, it's a pretty rare card, so I've only got one.

    On my profile page, I can see all my collections and the current value of those cards:

    And because entering this data can take a long time, it was important for me to have a CSV export so that I can download my cards and their quantities in a standard format.

    Tech Specs

    I could write several blog posts about the tech problems I solved making this app. And in fact, I probably will, sometime in the next... time. If you want to see a writeup on any of the features, leave a comment!

    At a high level, the frontend website is a fully static Next.js application. This means that the website is written in React and TypeScript with anything that can be rendered ahead of time written to static HTML. It's currently hosted on Vercel, but I could just as easily host it anywhere else because, again, it's static HTML. If Geocities was still around, I could host it there.

    That would be a bad idea, I would not host it there.

    The backend is a WordPress theme hosted on Smolblog. Remember that? The static rendering uses GraphQL to get the cards and sets, while the more interactive features use custom endpoints in the WordPress REST API. The only reason for the separation is... that... I couldn't figure out how to make the custom endpoints I wanted in GraphQL and I didn't feel like taking the time to learn it just yet.

    But there were plenty of fun problems I did solve, including

    • Excluding browser-only Javascript from static rendering in Next
    • Setting up OAuth so that it works with WordPress multisite correctly
    • Writing TypeScript types for a React component that didn't include them
    • Using basic card data and an MD5 hash to find different printings
    • Store authentication credentials in a cookie
    • Use React context to access authentication details throughout the application
    • Set up custom tables in WordPress and use them with the REST API and GraphQL

    The Future

    As I get to the end of the first version of this project, I learned an important lesson:

    WordPress was a bad choice.

    I don't say that lightly. I've spent the last few years of my life immersed in the WordPress world, and I truly believe it can be used for almost anything.

    But in the case of Grimoire, the data does not lend itself to custom post types as easily as custom tables. While sets and cards could conceivably be custom post types, they would rely heavily on custom metadata and taxonomies. The data is much more suited for a traditional relational database. At this point in the project, WordPress is only being used for authentication and as an administration tool. For the future of Grimoire, the benefits of a fully-featured platform like WordPress are outweighed by the difficulties in working directly with the database.

    I have a few plans for Grimoire moving forward:

    1. Rewrite backend in a modern framework like Laravel or Ruby on Rails. This will making with the database much easier.
    2. Consider using Next.js' server-side capabilities. This could take some pressure off of the backend by moving some functionality closer to the edge.
    3. Add detailed card information. This will only need to be stored once per printing and can enable some fun features like finding recent cards that work well together.
    4. Sync inventory to TCGplayer. I'd love to use Grimoire to manage my inventory that I have for sale.
    5. Offer a "Pro" subscription with access to historical pricing data and store inventory management. Because the people most willing to pay for something are the ones making money.

    I've rambled long enough. Go check out Grimoire and let me know what you think!

    → 9:28 PM, Apr 9

Slightly uneven since 2005.

Find oddEvan on

  • Micro.blog
  • Bluesky
  • Mastodon
  • GitHub
  • Tumblr
  • YouTube
  • LinkedIn
  • Read.cv
  • TCGplayer

Projects

  • Smolblog
  • PillTimer
  • oddEvan UI
  • madcrasher
  • Other projects

Archive

Links

  • Blogroll
  • Resources
  • Fun Times

About

  • About Istoria

Colophon

Typeset in Raleway by The League of Movable Type and Satoshi by the Indian Type Foundry. Powered by Micro.blog. All your base are belong to us.

© Evan Hildreth; licensed under CC BY 4.0.