Claude followed a spec that I gave it from ChatGPT... but oh MAN was it rough code. The reality is that it worked -- but it was pretty gnarly. So in this video I walk through how I can start to clean up all of the dependency injection to use Needlr.
View Transcript
We got to just delete a whole lot of code just to show you. If I get rid of that wrapper, that's all we needed to do to set up Needler. I had Claude Code implement a software system for me based on a PRD built by Chat GBT and it mostly worked, but the code that it created is pretty nasty. I could have spent a little bit more time upfront refining the PRD so that it would organize the code better, have it architected in ways that I'm more comfortable with, but that's not what we got. So, in this video, we're going to look at refactoring some of this application so that we can change up the dependency injection to use Neler, which is a opinionated dependency injection assembly scanning type registration system that I've put together. It's based on concepts from Autofac as well as Screwdor.
It is opinionated, but it's what I like to do to build plug-in architectures. So, we're going to start off by cleaning up the program.cs file. And hopefully, we can see by the end of this that it looks just a little bit less nasty. To kick things off in the program.cs file, the LLM did not follow the guidance around using Neler, which is the Nougat package that I have. And I just wanted to show you what it looks like to start with. So you can see the beginning and by the end of this hopefully this is only a couple of lines long. What we have at the beginning is your traditional setup for an ASP.NET Core web server. So we have the builder up at the top and then you'll notice there's a pattern. It's pretty common actually that we have extension methods used for registering
dependencies onto the container. It's just not my preferred way to do it. But when I told it to go build plugins, what it took that as was adding extension methods for organizing some functionality. So it has a SALOG plugin. But if we jump over to it, it's really just an extension method for configuring Sarah log. Okay, not exactly what I wanted. I didn't want an extension method, but that's how it did it. Same thing with Postgress. If we jump over to the Postgress plugin, it's really just configuring all of this stuff that it has in a what it called a plug-in class. But if we go through the other examples, it's basically the same thing. So these plugins that I have highlighted, these are all done as extension methods. If we go a little bit lower, we have some things that you have to go
register onto the dependency container. And again, this is pretty standard. If you're familiar with building ASP.NET Core sites, you're used to doing this. you're adding more functionality. You got to make sure your type is registered on the dependency container. Little bit lower. We have to make sure that we're using controllers in this setup. That's how it did it. It's not using minimal APIs for everything. It's using controllers. Has swagger some health checks. So, we go a little bit lower. We see that it's adding the health checks here. And then it actually maps the health checks a little bit below. So, little bit more generic kind of setup here. and we're going to start pulling all of this apart. So, it's going to be a little bit nasty for a bit. It's probably not going to compile for a little while or if it compiles, it's
certainly not going to run. But by the end of this, we'll have all of this stuff pulled out. To kick things off, the first thing we need to do is go get the Nougat packages. So, we'll go over to manage nougat packages. Needler right now at the time of recording is just in alpha. Uh I'm I think the only person in the world probably using it. It's not showing up because I picked installed, but we have Oh, there's a few downloads. I wonder if those are all me. Um, we're going to use uh the ASP.NET one. So, let's grab that. We'll install it. There are some packages that you can pick and choose. If you want Carter to map your API endpoints, if you want to use Screwdor for dependency scanning, you can do that as well. So, there couple different variations. We can see
that there is a conflict because it's .NET 8. Oh, I didn't even think about that before recording this. Let's just hope that works to kick things off. Let's try building again. Otherwise, this video is ruined. Drum roll. Success. Great. Okay, so let's go ahead and try that again. We'll go install this. Boom. Okay. And so for a little bit of background, if you haven't watched my other videos on Needler, what we should be able to do, if I scroll right back up to the top, there is a fluent builder syntax using a type called a syringe. Because I'm just being punny, we can make a new syringe and that comes from the needler injection name space. So really what it should look like in the end is something like this. So like one line to go build it. It'll be a little bit more complicated
than that. And then we can say, right? So, I want my entry point to be literally almost this simple. Couple other things that we might add, but it should be very, very simple. Like I said, it's probably not going to compile for a little bit, but like we don't need that anymore. We don't need the builder. We're going to start pulling off these pieces that we see that are plugins into a needler plugin. So, let me jump into this one and we're going to look at how Sarah log is registered. So, what I'm going to do is make a new Serogg plugin. And if I look at how this is done, I got to kind of go between these two methods. I'm just looking at the dependencies. So, it needs the configuration. It needs a host builder. That's fine to call you Sarah log. So,
in my case, I need to use the web application builder. So, I have a plugin for that. There we go. There's the web application builder plugin. I will implement that. And then we should be able to basically move this code that we have here. Couple things. You can see that this old Serogg plugin, it's calling configure seralog. We're not going to want to do that, but I'm going to go take this code, pull it up to here. We will do options.builder host use cerilog. So we need that. And keep in mind the order of operations. So this code configure serial log came before. So whatever we were doing, I'm just going to move it. It might not be right, but let me put it here to start. This is essentially what it did, but we don't have access to configuration, but we do because it's
on the builder. And then we can pull the configuration off of it. There's more cleanup that we can and should do, but I'm just going to do the minimum to get this compiling. So now we can go ahead and delete this code. What I want to let you know is that when we have a plugin with Neler that's like this, we just define it and we don't have to touch it. So we don't have to go register this or call some method to go use it. We just start deleting code because this will automatically get picked up. So let's remove some of those name spaces at the top. And now we start deleting. It's going to feel pretty good at least for me because I'm the one backspacing the code. Let's go into the next one and we'll repeat the same process. This is for
Postgress. So I'm going to do the same thing and I need to figure out what type of plugin I need. So again configuration and a logger will have access to both those things. What else do we need? We need this is right onto the service collection. Okay. So I have another plug-in type for that. And if you're curious about what plug-in types there are and sort of why I have different ones, it's really about the lifetime of when we're setting things up. So a service collection plugin happens very early. The web application one is for configuring the web application itself. There's some other plugins that run after everything's configured. So there's different parts of the life cycle where you can start setting things up. In this case, based on what we see down at the very bottom here, right, it's adding a transient Postgress. It's
adding the connection. I'm just going to call out I don't like this. This is not how I would go do this, but we're going to do the minimum to get this running. So, let me take that line, put it up here. Options dot services add transient. And I will try to get this, I don't know, slightly more readable. Let's do that. Okay. So, now we got this part out of the way. We can get rid of that code, but we still have all of this other stuff. So, this is got to be pulled up. Let's see how we're going to do that. They're registering this entire plugin. What do we want to do? This is actually more like a factory. So, I'm going to keep this code. I'll explain how I would normally do this, and we might actually be able to get away with
it. So I'm going to do a Postgress connection factory. We're going to have that. I'm going to just call it that. I have the same name up above. So I just got to be careful and move these so I can read it a little bit better. But we have this factory. It doesn't like the visibility of it. Oh, cuz the logger. Okay, there we go. Have this one wrong now. Okay, so what we're doing is kind of changing up the behavior a little bit. It called it a plugin. To me, this responsibility is what does it do? It creates connections. It creates things. It's a factory. So now, I don't think that this factory maintains state. Maybe arguably aside from the data source to be honest, I haven't thought this through yet because I don't know the application well enough. This is a very new
application for me that AI built. I was thinking that I would like to move this away from the add transient part, but that's okay. I think what I'm going to do is keep it this way and I'm going to explain normally what I would do when I have a little bit more confidence about the lifetime of these objects. If I have a factory like this, my factories never have state in them. They are purely just to create things. And that means if I have a factory that is purely to create things, it doesn't need to be added as transient or scoped. I can simply have it as a singleton because I never need to make new instances of the factory itself. I have a sneaking suspicion that this SQL data source with this connection is not going to do what I want. So, I'm a
little bit nervous about that. But one thing that I will do once this is up and running is I would like to play around with changing this because if this truly could just be a singleton, we can actually delete the whole plugin which is the really cool part because what needler will do is it looks for classes if it has enough information to be able to instantiate them. You don't need a plugin for it. it just goes and registers the type and it will be able to add it to the dependency container automatically for you. Again, just to reiterate, if this can have a singlein lifetime, we can just delete this plugin entirely. The only reason this plugin exists right now after this refactoring is because we're explicitly saying that we want it to be transient. The default behavior for needler is singleton. Let's go
ahead and move on. We'll go back to program.cs CS one more to take off the list. There's a couple more to get through, but it's going to follow a similar pattern over and over. The thing that I really don't like about this one when I saw it earlier is that this is trying to register this type handler. And that's because we're working with Dapper and it's going to have uh basically this vector type handler. You can see right here like all of this code in this file. If I quickly scroll through, everything you see is just to be able to run this line. It's so complicated that there is a lock. There is a background service. I personally think that this is so much extra stuff that we don't need to do. I think that what I'm going to do is this instead. We're going
to do a internal sealed class. Um, we'll see if this actually works, but we'll call it type handler plugin. What type do we need for this plugin? I think that we can get away with one of these. This is going to be a bit of an abuse of this plug-in type. But like I said, I think the only thing that I actually need, let me just cut that out. And what options? Oh, I don't know if I have a logger at this point. I don't think I have a logger. So, I don't know if I care enough to do that. Anyway, to make it really simple, I think that's all we need to delete all of this code. That feels good. Okay, so we'll get rid of that. Actually, let me bring this back for a second. Was anything using PG vector? No, I noticed
that this using was gray at the top. So, I wanted to double check that. Nothing's using it. Not a lot of code there. Let's go back to program.cs. One more off the list. We're getting there slowly but surely. Next up is Semantic Kernel. Okay, this one's going to be a bit more of a beast. There's a bunch of code up here. Semantic kernel is interesting because Semantic kernel itself also has a plug-in system that works with it. I don't think that we're really using that yet. This might be okay. But let's do this. What do we got in here? Semantic kernel Azure OpenAI plugin. Okay. So, we can see on this one for the dependencies, it's a service collection and a configuration. So, I'm going to do this internal sealed class. We'll do an ice service collection plugin. We'll get that from needler. Okay. Implement
it. So to kick things off like I've been doing in all of the other situations take this drop it in and then on here we have let's do this options services. Okay we need configuration and I think I have that on options config. So all of that code compiles right now which is lovely. Again do I love this? I'm not a huge fan of it so far. But the thing that I really don't like personally is that we still have this going on. This code here I'm assuming is again more like a factory. So let's get rid of that old extension method. If we look at this, I feel like this is it's literally building the kernel, right, with some configuration registering things onto it. Building the kernel. Sorry, this is the kernel builder. And then it builds the kernel at the bottom. This thing
is a factory. So I'm going to call it semantic kernel factory. Let me update some of these things. Again, this is just naming conventions so that when I'm going through this, I feel a little bit better about what's going on. This is kind of an interesting pattern. You can see that it's adding the singleton here and then it's getting the required services. I actually think based on what I see here that I'm going to delete this line. Maybe I'll comment it out just for now. And what we should be able to see is that needler will actually go register this automatically for us. It will pick it up and then it will be able to resolve it. If we don't want that to happen, there is an attribute called do not autoregister. And then I can say I need to be the one to do
this. But I think if it works how I expect, needler will take care of this. Which reminds me, the other thing that we saw over here, I actually do need to put do not autoregister on this. The reason is because we have a plugin that is manually doing that work for us. I forgot about that. Good catch, Nick. Okay, so at this point, we have a factory and we have a simple plugin to go configure things. Just double checking. Yeah, right now the way that these configurations work is they need to operate like this. There's probably a pattern we could come up with that does this automatically for us, but not right now. Okay, so serial log, we got this one. Semantic kernel, we got this one. Postgress connection and Dapper vector type. So, making good progress. One more off the list. Now, we got
some of these guys. And I think what we can do just to make it super quick internal seal class. It seems to be learning, but we don't need that necessarily. We can do an I service collection plugin. Let's Copilot didn't do exactly what I wanted. That's okay. But we should be able to take this. Again, not necessarily how I would organize this, but we're just going to get it going. So, ideally, like I checked earlier, markdown chunker has some state in it. Actually, is that that's just configuration. We might be able to make this thing a singleton, but I don't know if I want to risk it. Maybe we'll try at the end. We might actually be able to delete this whole thing, which would be super cool. We'll try, but not right this moment. So, we have this plugin and what we can do
get it out of the entry point. What's next? We have some stuff. I want to do the health checks and general service stuff separately. What I will do is something like I don't know like a generic I don't have a good name for it right now but internal sealed class web plugin just for now we will go move some of this stuff into here that compiles I'm going to do the health checks after I think for cores I will pull that into here as well just to kind of group some stuff now health checks we can go pull but I need this as well. So, one sec. I'm going to do a hybrid plugin. So, this code here that you see, you can see it's doing swagger configuration. So, I'm going to take that. I'm going to pull it back up to here. I'm going
to make this two types of plugins. And this is going to be web application plugin. And we'll be able to do this. And we need just to make this easy. Thanks, co-pilot. I didn't call it that though, did I? Web application. Okay. So, this is a dualpurpose one, right? But the nice thing about this is that I'm grouping mostly like-minded stuff together, right? So cores maybe not, but this does some swagger stuff and then this part down here also does some swagger stuff. We could split this again and do like a swagger plugin and then a generic web plugin. I'm just not going to be super concerned with that for now. Pull it out, right? We'll keep going. I notice Sarah log request logging. What we could do is take this out. We're going to go back to our Serogg plugin and we're going to
do the same type of thing. We're going to make a hybrid. Okay. And then options web application. Okay. There's going to be I notice a couple of other things like this, which is okay. HTTPS redirection, cores, and map controllers. Where do those go? Well, I have that generic one, which is just web plugin, and I'm going to drop them down here. But again, we could have a corors plugin. So, I could pull out that and this. There's all sorts of options, but it's just how you want to organize stuff. The nice thing about or hopefully you think it's a nice thing, I appreciate it at least, is that when we start pulling these things apart, I can organize them in their own way. Yes, you can do this with extension methods. We saw some of that earlier, but Neler will automatically go find these things.
I never have to go add more stuff onto these two lines. I really want to see if we can get this down to two lines of code, cuz that would be very exciting. We're almost there. I think we have just the health checks left and oh, and this API. So, we got two more plugins to make, but we're almost there. So, next up, internal sealed class health checks plugin. And then we need that on the I web application. Sorry, the builder first. So, this comes up to here, options dot builder. Okay. Okay. And then we have to go use these on the app itself. I'll pull those up here. Now, this is going to be another dualpurpose plugin. Okay. We'll get that bunch of code there. So, we're going to do var app. Again, the implementations I don't want to spend a lot of time
on cuz this kind of stuff is likely going to get refactored, updated, changed. This code will be online so you can explore it as well. I just want to show you the organization going to needler and how it cleans things up. Now, we take this and we get rid of it. Okay, we got a couple things left. Technically, we can do this as well. This I health check. I should have checked this ahead of time, but I think it's not happy because of how some of this stuff is registered. I don't know if I make the health check internal if it's going to complain. Shouldn't. So, let me do that. Otherwise, I just go make the Postgress stuff public as well. This one, it's okay, it seems. I got one more error. Where are you? Here it is. connection plugin. Oh, that's right. Okay. So,
I think that's okay. This one was public anyway. Let's just move this back to public just in case. I don't know if the health check stuff will complain, but we can go move these out. Again, this isn't needler specific. I'm just pulling those out. But we're getting there, right? We got one more plugin to make this guy right here. So, let's take that. Let's do it. Implement the interface. And this is options dot web application. And this is actually the ingest plugin. So it looks like the other ones were done by controllers. The other methods we have our lookup and ask. It made these two as controllers, but the ingest, it didn't for some reason. What's super cool, I mean, we can keep some of the logging stuff if we want. If you want to see that, maybe. I don't know. Let's pull this guy
out. We don't need this builder. If we don't put the logging around it, like I said, we got only a couple lines. If we want to keep the logging around this, we can do that. And there we have it. It's done. But does it work? And I don't think that this stuff ever works the first time. And that's okay cuz one of the things I think that we need to do is this. We need to use an assembly provider. And what Needler needs to do is understand which assemblies that you want to pull in. And there is a builder for that. So, we're going to ask the builder to get the matching assemblies. We'll pull that over. What this is going to do is scan for us. So, directory, we'll do app domain, current domain, base directory cuz that's where we're running from. Sorry, that's
where the actual binaries are. And then a file filter. So, the file we will say couple things. We need a file whether it contains by the way matching assemblies will look for executables and DLS. The file contains this which is sort of what whatever Claude decided it was going to name this and just so I can sleep at night. We will do this and then we're going to copy this. Now I'm also going to ask it to scan things that are from needler as well. And I think that's mostly it. Except we didn't call build on this one. And now I think just maybe just maybe this will work. But things never work the first time. But let's see how we do and see if we missed anything for the configuration. Okay. So as expected, there's something blowing up. But that's okay. We'll scroll all
the way up. What is it complaining about? They're not able to be constructed because singleton implementation type. What this is doing is I forgot to put an attribute on some things. I didn't follow my own advice, Nick. So that's okay. We're going to go back over to here. These health checks that get added, we do not need to register them automatically. Okay. So we have a plugin that does that for us. Most things that I build I don't have to autom or I don't have to manually register stuff. So those I missed. We might see some other ones. But that problem should go away now. For extra context. Needler was saying, "Hey, let me go build these things for you." And it doesn't work that way. So the health checks don't at least. I'll try again. Okay, we got some other error. Service descriptor for
semantic kernel functions. Unable to resolve service type for postrest connection factory. This is going to be one of the issues with I believe singleton versus some of the other types. Like I said, most of the stuff I build is always singleton. So I think what we might try is getting rid of this plugin because it's trying to do it as transient. I don't think this thing has any state really and I have to get rid of the do not autoregister attribute. and we can try again and see. Maybe Needler will get it all right this time for us. Okay, we're getting a little bit closer. I think it's coming down to some of these other things that are still manually registered as different lifetime scopes, and I don't think that they need to be. So, let's go check out one more spot. We got the
lore rag plugin. I think that all of these things are stateless. That was configuration. I'm just doing a double check, by the way. Lore retriever like this is stateless. This is stateless. My repository is hopefully stateless because it has a factory to go make new plugins. I'm feeling pretty good about that to be honest. Let's just comment it out so we don't lose it. Try one more time. And we successfully made a needlerbased web application just with a few misses, right? But let's see, does it actually do the work on the other APIs? And there we go. We have our entire system working end to end because I'm able to go ask the LLM about the information that it has done embeddings on based on a bunch of wiki files. So, just to quickly show you, if we jump back, we can literally delete that
file. Get that out of there. And then I'm going to go back over to here. We can delete this as well. So, we got to just delete a whole lot of code. And really just to show you if I get rid of that wrapper, that's all we needed to do to set up Neler everything else. And if we keep building more features and functionality, it will automatically get discovered. But just a couple of things to remind you is that I don't generally have state in my classes. I like making classes so that I can organize my thoughts and functionality, but the state that I have is generally references to other classes. And usually something like a cache like using fusion cache is probably the only like sort of stateful thing that I'm going to have. But that's actually shared state that I would want to
have to begin with. So most things I get away with singletons. There are a few exceptions where I do have to manually register. But as you saw, even what I thought would be exceptions based on what Claude had built for me, those are not exceptions. truly they can be singletons because there is no state. Overall, we can simplify the entry point. I will make follow-up videos where we have this more organized and we can start adding more functionality and refining other parts as well. So, thank you so much for watching. I hope that you thought that there's a little bit of benefit to having Neler to be able to organize these things for you. Personally, I think having, you know, 13 lines of code is a little bit nicer than that big entry point that we started with. So, thanks so much. I'll see you
next time.