 So I'm just gonna hook myself up. All right, hello. My name is Ted. You will find me online in most places as Dren me. I will try not to overshoot by too much. So for one, I'm a Rubikop Core Team member. We're currently in the works of releasing Rubikop 1.0 and we're looking for new contributors. So if you can code, that's great. If you cannot, that's also great. We're looking for people to help out with everything from fixing bugs to creating new cops, triaging issues and improving the documentation. So if you're keen, you can come and talk to me after the presentation or you can ping me on the Ruby SG Slack Group. And if you're not in the Ruby SG Slack Group, you can go to ruby.sg and invite yourselves. When I'm not working on Rubikop, which is most of the time, I am the VP of engineering in a company called Engaged Rocket. We build software for HR professionals. Our office is in block 71. And we're also hiring for all levels, interns, juniors, senior developers. So if you're interested in the challenges that I will be talking about in this presentation, you can come and talk to me or you can ping me on Slack. So to frame the discussion and provide some context for this talk, I'd like to introduce you to the challenges and constraints that we have in Engaged Rocket, building HR software. So for our Rails application, like for most, the technical context is not really that high. The HR space is not very technically sophisticated yet, but we do have to deal with a high level of information complexity. So the keyword for us is feedback analytics. We take a lot of structured and unstructured feedback from employees and we try to present this to HR professionals in ways that are understandable and actionable for them. And although that concept sounds rather simple, taking a lot of data and presenting it to someone, it's not always an easy task. And our biggest challenge is dealing with the sometimes overwhelming information complexity, trying to choose the right abstractions and still being able to ship features. So how are we accomplishing this? Part of that question I hope to answer in this talk, which I named Mind Your Model. And I talk a lot about modeling in this talk, but it's worth mentioning that the first part, which is the mind, is at least more important than the model. We need to be mindful in order to mind our model. So what do I mean when I say the model? To explain that, I will show you a small illustration of how I like to think about our code base in Engage Rockets. So at the heart of our application, we have our data model and these are essentially all our records that are backed by a database. Outside that, we have the extended domain model. This contains some records which are things like null objects with static attributes or records that are aggregates that pull their data from the collections from the data model. And that represents sort of the inert domain model. It doesn't really do anything, it just sits there waiting to be acted upon. Outside that, we have our use cases, which sort of grants the domain model life. It allows users to perform actions on the domain model and change its state and ask questions about it. And on the very outside, we have the UI, which can be anything from a webpage to a PDF document to an email that is being sent to the users. And this model can apply whether you use Hanami or use Rails. It's just the implementation details that's different. And when I talk about the model, I'm specifically referring to the inner parts, the core. And in here, we sort of try to model the universe of our users, which is the HR professionals. We're trying to model organizations and employee hierarchies and such. And I will talk about two properties of our model that we are trying to aspire to in order to deal with the information complexity that we have in the product. I'll talk a little bit about accuracy and I will talk a little bit about robustness. But I would like to start with a disclaimer. So I will be outlining a lot of tactics in this talk that we have adopted because they solve specific problems that we have encountered in building engaged rockets. But please be mindful that it doesn't mean that you should go and apply all of them in your own project. Always start with a problem first. And maybe some of the solutions that I present today can help you out. So when I say accuracy, I'm talking about how well the domain model maps to the universe of our users. Not necessarily in the sense that it models perfectly the physical world. I think the world is a bit too fuzzy for that to be feasible. And a lot of times we're solving human problems which tend not to be constrained by reasoning or logic. When talking to business users, you will also often hear different stories about what their domain looks like or how their systems work. So an accurate model to me is one that supports the use cases that solve problems for users. And ideally, it should do that with the lowest amount of complexity possible. More than that, I think an accurate model can sometimes help us find the real problems. It can often be hard to get to the real problem when talking to a user. They might start by presenting you with their imagined solution or they might give you an XY problem. And to frame the talk on accuracy, I would like to ask a question. How much deliberation should we put into a given technical decision? Is it 10 minutes? Is it one week? And as with many hard questions, the answer is that it depends. And I would like to say that it depends on the cost of reverting or revising this decision later on. Or in short, the amount of deliberation should be proportional to the cost of change. If we put too much deliberation into unimportant decisions, we'll be ineffectively spending our time and energy and we'll end up with a bike shed. If we put not enough deliberation into very important decisions, and this happens a lot because we might be too delivery focused, then we are on a death march to an app that will eventually calcify and resist any future changes. So knowing that, we need to ask ourselves, what is costly to change in our Rails app in our case? And particularly one with live users and a lot of live data. And generally changing the data model is not only expensive, but it can also be anxiety inducing. So to change the data model, we probably need migrations to describe the changes to our database. We might need to rebuild some of the database indices. We might need strategies for backfilling data that we haven't captured in the previous data model. We might need to use multi-phase deploys to migrate user data to a new column and in a subsequent deploy, remove the old column. And we might need to fiddle around with the console with our production data, usually late at night or on a weekend when our users are not active. So it's not a very nice place to be. And anecdotally, it seems like the cost of change increases the closer we get to the heart of our application. Part of this is the implication of dealing with live data, but also a problem in our data or domain model tends to have implications for our use cases and UI. So amending them usually means amending the use cases and UI which compounds the cost. So it seems natural then that we should spend some amount of time and effort on getting our data and domain model right. And one good way to get started is to create an entity relationship diagram which is a complicated name for boxes and arrows diagram. So a full ERD would contain the names of the entities and it would have some cross-feeds to indicate one to many relationships. And ERD is a good way of uploading our working memory as well. As developers, we're very good at complaining about distractions, but this is something we can actually do to increase our increased robustness of our personal workflow by taking away the amount of things I need to keep in my working memory. And it's much more pleasant to have a document that I can look at rather than going to every single Ruby file looking at the associations and trying to memorize what the domain model is. It can also be a great artifact to have when we have design discussions. So just like how business users can have different ideas of what the domain looks like or how the system works, this can happen to developers as well. It's very helpful to have one document to look at and point at together for a shared context. Because ERDs are diagrams, it's there are also some things that we can visually identify by just looking at the diagram. So if we look at the left side of our diagram, it all looks quite neat and simple and easy to comprehend. If we look at the other side, we might or might not have cause for concern. There are some circular references and entities that have a lot of connections to other things. This can be a way to identify a problem area in your app where you might be able to identify and extract some domain concept that you were previously unaware of. Or it could just be necessary complexity for your app. So again, we need to use our own sound judgment for this. We can also use the visual nature of the diagram to help identify module boundaries. So in our example, we had two sort of islands of entities that are connected by a very simple bridge. This could indicate that we're dealing with two unrelated parts of the domain. If you want to maintain this relationship simple, which is often desirable, we can do things like namespace our models or we can extract Rails engine, which William has talked about in a previous talk. Sometimes these islands are also completely disjoint. So there's no connection between them. It makes it very easy to see that your app has different apps inside. If you want to get started with ERDs, you can use the Rails ERD gem. It works out of the box and it generates good enough diagrams for most apps, I would say. And it also comes with rake tasks so you can automate this as part of your build process. And it will essentially put a PDF file in your repo, which you can use for the current ERD. In Engage Rocket, we like to keep our use cases away from our domain objects. When not doing this, we found that the domain objects have a tendency to get overly specific and coupled to the use cases. And disparate concepts have a tendency to get fused together into fewer records. And this usually only lets us fit so many use cases until the use cases start to conflict with each other, whether it's through callbacks or conditional validations or otherwise. But this is not the only way of building Rails apps. For us, we found it helpful to use service or use cases and presenters to avoid the problem that Gregor was talking about, of having fat views or UIs with a lot of logic in them. So we currently have 81 service objects and I think 14 presenters for complex UI elements. And being able to reason about this and test them in isolation has been useful for us. As a bonus, I think it makes our code base quite discoverable. If you need to figure out what the app can do, you can go into the services directory and you can look at the names of the services and instantly figure out anything the user can do. Or if you need to work on the calendar widget, you can go to the calendar presenter and start working. But there are other ways of building Rails apps that are conducive to other constraints. So DHH has a video series online where he outlines how he builds Basecamp. And the approach he takes is different from this one. He uses a lot of callbacks and he doesn't make the clear distinction between framework and application and model and business logic. So what I would recommend is go visit those videos. You can look at the video I made a few years ago about the beyond the MVC on the extended object taxonomy we're using. Then you can look at your own problem and you can shop from your list of solutions. Another tactic is don't lose your data. We had a new feature request last week which on the surface seemed like a relatively straightforward one. To illustrate, I need to explain what a 360 review is. Essentially, you ask your team members to review you and you review yourself and then you compare the results between you and your team. Your reviewers can be assigned either by yourself or by an admin or someone with the right to do so. This requirement said that in the email we send to the participant, we want one list of the reviewers that they added and one list of the reviewers that were added by someone else. Straightforward, except we didn't have the data in our data model. Basically, we're missing a reference to the user who made the assignment. In this case, it was a complete oversight on our end. It had not really surfaced in our design discussions and until this rogue requirement that came much later, we didn't really rely on this piece of data for anything else. Because we cannot reliably determine the assigner post-factum, we had to sit down and create some rules for backfilling the data. This doesn't have a huge impact on our existing users because it affects mostly historical reviews, which are no longer sending out these emails. But it did probably require more engineering effort than it would have taken to put this data in from the beginning. And it also impacts our internal data analysis a little bit. Great, we have established how to create an accurate domain model, one that clearly reflects the domain we're working with without getting too intimate with specific use cases. And we also had some handy diagrams to look at to inspect it. But even now, we need to ensure the integrity of our domain model. It doesn't really matter how accurate it is if we still get nonsensical or invalid data stored inside the domain model. So I would like to present to you with a very non-hypothetical scenario. Imagine you're on duty, you're monitoring sentry, you experience some issue, you start looking into it and you find a no method error in one of the views. What do you do? And actually this is how we discovered a lot of problems with our domain and data model. So a suggestion could be that, oh, we had a nil check in the view and that's it, right? It only takes about 30 seconds, which is pretty good compared to, say, a seven day SLA. And the ROI seems through the roof because 30 seconds is a very short amount of time. But the answer is no. Because the problem is not that we needed a nil check in the view. There was a problem with our domain model and fixing it in the UI is directly counterproductive. Because a problem with our domain model probably have much more far-reaching consequences than just a view breaking. And we're sort of patching this over and waiting for the next thing to break. And by fixing the proximal cause, which was in the view, we're actually adding complexity to the outer layers of the app, which is there only to account for things that shouldn't happen in the first place. But I do see really good engineers all in this trap all the time. And we actually know why it is so tempting based on one of our previous slides. And that is because it's super cheap to fix it in the view. I can just click that finish button in Pivotal Tracker and get my fix of dopamine for the day. But we're really playing in the immediate term by doing this. And as professional developers, we need to play in the long term. We need to play with the team. So what we should do is we should find the real problem, add a regression test for it, fix it and then submit the peer. But luckily there are a few ways we can increase the robustness of the domain model. And I think the first and most important one is to establish some invariance. So what is an invariant? It is a fancy word for saying a condition that we can assume be true during runtime. So it's just something we assume to be there. This is also the hardest one because it's conceptual work. You need to think about the product. You need to think about the code base at runtime. And it normally requires some collaboration with the product owner. Sometimes we do establish invariance without being aware of it. And a large portion of our application ends up resting on this one assumption. This can be very dangerous because there might be an emerging requirement that contradicts this invariant. And undoing it might unwind the entire application and cause a lot of issues down the line. So invariance can be conceptual. This usually means they are explicit in the product and the domain itself. So I have some very contrived examples. We can say that an employee must have exactly one direct manager. So this is something we can decide together with the product owner. By saying this, we don't have to account for the case where an employee is orphaned, has no manager. We don't need to account for the case where there are more than one direct manager. So this can greatly simplify the complexity of the use cases. But invariance can also be established by developers alone in the implementation details. This is normally not explicit in the product. So it's something we can do under the hood without affecting the user experience. And it can still help simplify use cases and UIs. So for example, in an app where we have users, some of them have accounts, some do not. We could, if it simplifies our application, decide that, okay, everyone has an account, but the users who don't have access, they have locked accounts. Now the invariance we have established is there's always an account there, which helps simplify the logic a bit. Or we could use a null object to say that there will always be a current user, whether they are signed in or not. So instead of always checking for the presence of a user, we can check whether the current user is signed in or not. Which is conceptually easier to understand. But probably the most straightforward thing we can do is to use the facilities that are only provided in Rails for ensuring the data integrity. And the most common and low-hanging fruit is to use validations. In Rails 5, belongs to associations have presence validations by default, which helps keep our domain model somewhat intact. So in the example, we're using some Rails validations together with a custom email formats validator. And we recently had a problem where some emails had non-UTF blank space characters inside. And we think this was happening because users were copying emails from another app and it copied some strange characters with it. This caused some problems for us. We also added an email sanitizer that could take care of this. For records which can be destroyed, it's usually a good idea to establish some dependent strategies. Let's tell us what will happen to associated records when we destroy the parent record. This forces us to think about the domain model. How should it actually work? What are the implications? And it also gives us some automatic treatment of associated records. And the most familiar and common strategy is destroy. So whenever we destroy a reviewer, we also destroy their assignments, avoiding orphaned records in our database. Another useful but maybe less known strategy is restrict with error. This will add an active record error to the parent object if there are associated records when you try to destroy it. Another good tactic is to use state machines. If you have some complex record that is part of a workflow, then it might be a good candidate for a state machine. A common indicator is that you have a Rails enum on it that says status. And a state machine helps avoid invalid transitions and invalid data states by having us declare all the possibilities upfront. We can also do things like guards for more advanced checks on transitions. And we can add some hooks to update some timestamps. And in the example I'm using the AASM, which is very comprehensive and well documented and I think actively maintained. So highly recommended. If we need to, we can use some database level constraints to enforce integrity. You may or may not need this in your applications. I think the official Rails guide says you won't need it, but it really depends on what kind of problems you encounter, where is your data coming from? Are you copying from other databases? Are you importing from other places? Or are you using methods that bypass validations, like bulk update, bulk insert? In our case, we encountered at least a few places where this would otherwise have inserted invalid data into our database. So we find it somewhat useful. So we can add null constraints to mimic the validates presence in Rails. We can provide defaults where they make sense. So in the example of migration, we are adding an enum column to projects. It doesn't make sense for us to have projects without a status, so we don't allow it to be null. And all new projects are pending, so we also add that as a default in our case. We can use unique indices to mimic the uniqueness validations of Rails. So in this migration, we're adding a unique index to ensure that no two users have the same email. We can use the foreign key constraints to ensure referential integrity to our belongs to associations. So here we are adding a company reference to the project table. And by using the foreign key constraints, the database will ensure that no project can point to a company ID that doesn't exist because the database will raise an error. If you're a power user and have a need for it, you can use check constraints, which are provided by Postgres. You can essentially get this to mimic any validation that you would do in Rails as long as the data is on the table itself. So in the example, we are adding constraints to the projects table to make sure that no project has an end date that is before the start date. And if you want to remove a constraint, you can just refer to it by its name after it's been created. There is one caveat to using check constraints. In order for the schema to be able to store information about it, you need to migrate your schema to SQL format. The Ruby format does not support check constraints. So to summarize, mind your model. Mind is the more important part. Always start with a problem. Be mindful of your model. Changing it down the line might be very expensive or in some cases impossible. Thank you. Mouse pads for questions. Yes. So you mentioned using database constraints, but then if you're using database constraints and application constraints, then you need to keep them synchronized all the time. If you don't use database constraints, then someone can put in value data. If you keep only database constraints but not application, then you get certain cryptic error from the database if you try to put in value data. So how do you deal with that? You can also not use constraints at all, but probably that's the worst solution. Yes, yes. So the way I've approached it is sort of a pick your point approach. I use both. And how do you ensure that you've got them synchronized? You just try. I ensure. Okay. Yes, I think there are some tools that attempt to solve this, but I don't think any of them have worked when I tried them. Some tools that can automatically translate your model validations into database constraints. This might also be one of the reasons why the Rails guys recommend just using the model validations. Because if you don't need the database constraints, it's an unnecessary overhead. But then the unique validation is there, right? Because you want something to be unique and you validate it before inserting the database, you might just go, you want to put the same records, try to put the same records, right? So then the unique validation might pass in the application because at the time, there is unique. And in the database, it will pass because you've got to put the validation and then you've got to validate other past validations, right? Yes. Uniqueness validations can also cause problems with things like sorting, like if you keep a sort index on your records and you try to reorder the records, you might bump into some uniqueness index problem as well. Any more questions? Yes? So you see, sorry, it sounds like both of your applications are more like web applications, right? I use SketchUp very extensively and I know that in SketchUp, right, you can create extensions with Ruby. So, I don't know. Just whatever you guys mentioned, apply to SketchUp as well because I totally don't know anything about Ruby at all. What is SketchUp? It's a 3D modeling software that's from Google, but it's not modeled by 3D models. You can use it for like a 3D modeling of buildings. You can use it to create like a, you can use it for 3D printing, virtual reality. So they have an extension warehouse. And the extension warehouse, right? All the applications are coded using Ruby. Yeah, but I don't know whether Hanami or whatever you guys mentioned would apply. I don't really know what the scope or extent of what SketchUp extension is, but I have a feeling that the part that applies is the use your mind part. Either I think the rest is more specific to application development. So, yes, mostly web apps, right? I'll hand over to William. Thank you very much.