Since last week, I could define a schema for a graph database. I also did my botany refreshment and defined my nodes and relationships. Implementation-wise, I have a recipe for building the form dynamically, based on JSON data generated from the schema, in Vue.js with Quasar Framework.
If I made my transformer in the backend right, this should allow me to change the schema in the future while still generating the frontend bits that depend on it. I've set up a stack with what I would call the VEGAN stack (lol, pun totally intended) - Vue, Express, GraphQL, Apollo, Neo4J. Or VEGANN (if you insist on specifying Node).
I only tested the front-end app with one type so far, but it should work the same for the rest. Both back- and frontend use WebSockets, and the backend pushes the JSON to the client when connected (if all goes well when the Vue app is mounted).
Saving data in the database is not yet achieved because I have to iron out a few things. For example, since I don't want the frontend to change each time the backend or schema changes, should I include GraphQL in the frontend? I initially thought "yes" because it would be easy to get stuff rolling. But, on second thought, a message to the WebSocket for specific information would give me the data I want. Surely my schema design would not put me at risk of over-fetching! (Of course, it will, but then I operate on the queries in the backend - no need to hard couple the front-end to the schema or queries - build the UI with data!). Ambitious, I know. But it's an exciting challenge.
On to the nitty-gritty.
Types & relations
Source
- where I found information or bought the seeds (or bulbs, plants, etc.). This would usually be a website with a name and the date it was used/accessed.CatalogEntry
- represents a species, subspecies, variety, or cultivar I own or plan to buy seed, bulb, etc. It is not the same as the seed, bulb, or corm. It holds "top-level" information about the plant. It should contain sufficient information not to require even "higher level" stuff (taxonomical information - because that's for v2.0, if ever). Some properties describe whether it's perennial or annual, how wide and tall it can grow, what distance it should have from other plants, crop rotation information, and names and images. It has relationships toSource
s and other items of the typeCatalogEntry
- as either companion or antagonist. It also has relationships withPropagator
s (for a single variety, I might purchase seeds multiple times, from different places, or sometimes I would buy seeds, another time a potted plant). Notes and observations for future records will also help.Propagator
- the seed, bulb, corm, or bare root plant. It is the physical means of propagation purchased from a source. It describes temperatures, germination times, sowing interval (for short-lived cultures such as lettuce or radishes), sowing depth, notes & observations, and a decision - to buy again or not.Culture
- represents the time and place where aPropagator
is in the ground, taking up space. It has a start and end date (optional), number of plants, harvested or not, and when, as well as any observations (what went well or wrong, recommendations for next time).Culture
s will have relationships to places in the garden (as I use raised beds, this will be easy) andpropagator
s (which have a relation to theCatalogEntry
, telling me what this thing is - i.e., Leek "Giant Winter").PlantingArea
- the raised bed. It stores the shape (circular, rectangular, triangular) and shape-specific size parameters, such as circumference, diameter, radius, or a given number of sides and their length. It has height, a location (aPoint
withx
,y
coordinates, and relationships toCulture
s.
Pheew. Now, of course, to keep the structure flat and friendly and the data consistent, in addition to property types such as String
, Int
, Float
, Boolean
, and Point
, I had to resort to enums. I have defined enums for any unit of measure (time, space, temperature), source types, planting area shapes, growth habits, and lifespan.
Some types share properties - such as notes, observations, and source(s). I had to carefully consider whether it made sense to use interfaces or not. I decided it made sense to use an interface for PlantingArea
(interface) - which has two shape-dependent implementations - PlantingAreaCircle
and PlantingAreaPolygon
- we don't care to see a circumference input field on a square, do we?
On the other hand, CatalogEntry
and Propagator
have some common properties, but defining an interface would be overkill. Plus, it's not like I would have a shared label for the two.
What helped make these choices was the question: Would I ever want to see the listing of BOTH CatalogEntry
and Propagator
simultaneously? No. How about PlantingAreaCircle
and PlantingAreaPolygon
? Yes. From a technical point of view, I would label these as PlantingArea
in Neo4J.
Minimum Viable Product
I initially wanted to build something mind-blowing that gardeners and botanists could use. While I don't dismiss that idea entirely, I do prefer to work by agile methodologies, so I had to decide what a minimum viable product (MVP) is. While designing it, I should keep in mind further development and new features as much as possible.
Since I build this for myself, I have decided that MVP v1.0 looks as follows:
An entry form will allow me to create all kinds of things on the fly and sync my state ASAP. So, if I have a new packet of seeds, I should put in the data for a
CatalogEntry
, thePropagator
(my seed packet), connect the two, add the sources and connect them to their respective node. I should also be able to createCulture
s andPlantingAreas
on the spot or choose from existing ones.While point 1. is achieved, I should refine and revise the schema in preparation for the "drawing board." The drawing board renders all my planting areas with a symbolic representation of the associated
Culture
items. I should be able to clone aCulture
, drag, rotate and resize it. Resizing aCulture
must calculate the number of plants. For aCatalogEntry
, represented by aPropagator
, I can choose companions from existing entries.In the end, I should have:
- a list of all the
CatalogEntry
items to be planted with a "need to buy" note and the number of plants/propagator
s I need; - a layout plan to follow - where to grow what, and with what.
- a list of all the
It uses Clojure for the back-end app and ClojureScript for the front-end app. Why? Because it's fun. ClojureScript might compile to JavaScript, but it's so much more expressive and concise and, mmmm, just lovely.
Next steps
Learn more Clojure(Script) - already well on the way with some fantastic courses by Jacek Schae , which promise to help me master ClojureScript front-end development with Reagent and re-frame. The link to Jacek's courses is an affiliate link, and I will get 10% of your money. A girl gotta buy seeds and fertilizer, yo!
Have another look at Lacinia, a GraphQL implementation for Clojure , because I can query the Neo4J graph with GraphQl.
If I still find it hard to wrap my head around working with GraphQL via Clojure, why not just Clojure and Cypher? Either way, I have 8-9 new technologies and tools to master. This is going to be lots of fun!
This week's lessons
Tackling Clojure(Script) again, I realized all the reasons to love and be intimidated by it. While last time, I allowed myself to be scared, this time, I go with love.
Carry smaller loads! I tend to overthink things. And perhaps a bit of inclination toward the waterfall method. The two combined would lead me never to build this product and use it.
Accountability works! Usually, my pet projects like this one end up in the pile of private repositories (read half-baked, quasi-efforts) because work or life takes over. Even if my accountability is to myself and the 1-2 readers accidentally ending up here, it's done wonders so far. (I could have been spending my time Netflixing instead, right?)
Refreshed the little knowledge I had of Clojure and its tooling, some Reagent, some more GraphQL, Cypher, and Neo4J. And I learned a ton more. The more I understand, the more I like it.
The freelance world hasn't changed much since 2015. It got larger. But better? I don't know about that.