Extending The NuSoft Framework
posted on 12/20/07 at 09:00:06 pm by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
Ok. So you've been using the NuSoft framework (or you haven't, but are thinking about it). One thing keeps nagging you, though.
What if I want to do something that's not in the framework by default?
Good question! The framework is built to be very flexible and allow you to do just that! If you're talking about extending Entities, there's two major ways:
- Add functionality to an existing entity
- Make a new entity based on an existing entity
Sticking with the Northwind database as a reference, let's say you want to change a few things when working with a Customer object. First, we'll look at adding functionality to an existing entity. A customer has orders, and an order has order lines, and order lines have products they are associated with. But a customer has no direct association with a product. What if we want to see a list of all products that a customer has purchased? Ideally, if we have a Customer entiity called customer, it would be nice to have that stored in a customer.Products property. But it's not there.
This is where adding functionality to an existing class comes in handy. We can add any functionality we want in the Customer.cs class file. We add the Product collection, and, following the pattern of lazy loading, we get this:
1: private EntityListReadOnly<Product> _products = null;
2: public EntityListReadOnly<Product> Products
3: {
4: get
5: {
6: If(_products == null)
7: {
8: _products = Product.GetProductsByCustomer(this);
9: }
10: return _products;
11: }
12: }
We don't have a setter because we can't directly change this collection - it's just a roll up of data available other ways in the system.
All set then, right? Well, not quite. We did the easy part. We have a collection, and we lazy load it. But where do we lazy load it from? The Product entity, but right now, it doesn't have a method for that. So, we need to add one:
1: public static EntityList<Product> GetProductsByCustomer(Customer customer)
2: {
3: string commandText = "ProductGetByCustomer";
4: List<SqlParameter> parameters = new List<SqlParameter>();
5: parameters.add(new SqlParameter("@CustomerId", customer.CustomerId));
6: return GetList<Product>(customer, commandText, parameters);
7: }
Obviously you need to create the stored procedure for getting products by customer, or if you decide to use dynamic SQL, then you can just write the SQL right here. In that version, it'd look something like this:
1: public static EntityList<Product> GetProductsByCustomer(Customer customer)
2: {
3: string commandText = @"
4: SELECT DISTINCT[Products].*
5: FROM [Products]
6: INNER JOIN [OrderDetails]
7: ON [OrderDetails].ProductId = [Products].ProductId
8: INNER JOIN [Orders]
9: ON [Orders].OrderId = [OrderDetails].OrderId
10: WHERE [Orders].CustomerId = @CustomerId;";
11: List<SqlParameter> parameters = new List<SqlParameter>();
12: parameters.add(new SqlParameter("@CustomerId", customer.CustomerId));
13: return GetList<Product>(commandText, parameters);
14: }
Now, when you call customer.Products, you'll get a collection of all products that the customer has ordered. That's it!
The second way to extent the framework is to inherit from an object. So let's do that. Let's create a summary customer object that shows how many orders each customer has. We could get that info another way - customer.Orders.Count, but that would require loading every order just to get the count - not efficient at all, especially if you want to display a list of customers.
So what can we do? We want the customer info, plus some more data, so we start with the customer entity as our base, and add a property for the number of orders:
1: public class CustomerSummary : Customer
2: {
3: protected CustomerSummary() { }
4:
5: private int _numberOfOrders;
6: [DatabaseColumn()]
7: public int NumberOfOrders
8: {
9: get { return _numberOfOrders; }
10: protected set { _numberOfOrders = value; }
11: }
12: }
Let's back up and explain a few things. We have a protected CustomerSummary constructor for two reasons: First, it follows the framework's pattern of using Factory methods to get and/or create entities. Second, a CustomerSummary object isn't what you'd be using to create a new customer - it's meant for displaying data. The [DatabaseColumn()] attribute is added to the NumberOfOrders property to tell the framework to expect this value to be retrieved when we run stored procedures or queries to get CustomerSummary objects. Now, like I said before, the CustomerSummary is read-only, so why do we have a setter for NumberOfOrders? Because that's the setter that the framework will use to populate the number of orders. We make it protected so only the framework can get access to it.
Next we need a method to get CustomerSummary objects:
1: public EntityList<CustomerSummary> GetCustomerSummaries()
2: {
3: string commandText = "CustomerSummaryGet";
4: list<SqlParameter> parameters = new List<SqlParameter>();
5: return GetList<CustomerSummary>(commandText, parameters);
6: }
To better reflect what's happening, let's take a look at the Dynamic SQL version:
1: public EntityList<CustomerSummary> GetCustomerSummaries()
2: {
3: string commandText = @"
4: SELECT
5: [Customers].*,
6: COUNT([Orders].[OrderID]) AS 'NumberOfOrders'
7: FROM [Customers]
8: INNER JOIN [Orders]
9: ON [Customers].[CustomerID] = [Orders.[CustomerID]
10: GROUP BY
11: [Customers].*
12: ";
13: list<SqlParameter> parameters = new List<SqlParameter>();
14: return GetList<CustomerSummary>(commandText, parameters);
15: }
First, no, I'm not positive that the group by clause works - basically, it's a listing of all of the fields in the Customer table (starting with CustomerID, since it's unique) because having a count() in your query requires that you group by all of the other fields in the query.
Anyway, does that look familiar? It should. It's very similar to what we had to add to the Product class to get a list of products by customer - the query is different, but the rest of the calls are the same. The call to GetList() is slightly different because it specifies we're getting a list of CustomerSummary entities instead of Product entities, but that's it. The pattern for getting a list of objects is the same whether you are working in a framework generated object or you inherit from one.
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
Using The NuSoft Framework, Again
posted on 12/19/07 at 09:00:45 pm by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
So you decided to jump on board and use the NuSoft Framework. You've been working on your project for a couple of months, and think to yourself, "What if I added coupon codes to the order so customers could get a discount?" Your response: "Sure, I can do that!"
But what you're really thinking is: "Now I have to add a CouponId to the Orders table, and add a Coupons table to the database. Then what am I going to do about my generated code? I've put modifications all over the place, and I don't want to lose them!"
One solution is to regenerate in another folder and merge your changes by hand. But that's error prone, and relies on you remembering all objects that might be touched by your change.
With the NuSoft Framework, there's another option. Regenerate over the top of what you already have. The framework is designed to respect your changes and ensure you don't lose changes you've made. So when you regenerate, you don't lose what you've already done. Some files will get overwritten, but if you follow a few guidelines about where you make modifications, then you should be OK. Before the guidelines, though, here's what gets overwritten when you regenerate:
- Any generated.<entity>.cs files: These are generated files, and aren't meant to be modified, so the framework will regenerate these every time. In the change above, this is what would add the CouponId property to your Order object, as well as give you a reference to the Coupon object. The .cs file will not be overwritten if it exists, but if it does not exist, then it will be created. So, in the above example, a Coupon.cs and Generated.Coupon.cs file will be generated.
- Framework/*.*: All files created by the NuSoft Framework and placed in the Framework folder will be overwritten. It's all generic code in there, so it gets overwritten whenever you generate - unless there is a *.cs and Generated.*.cs - then only the Generated.*.cs files get overwritten.
- Stored Procedures: If you're using stored procedures for your data access, then all of the stored procedures will be overwritten. You'll still have to run them against your database, but the files will be updated. This is important since all of the Order stored procedures would be out of date if you added the CouponId field.
Now, here's what isn't listed here: any solution or project files and your custom entity classes. Those don't get overwritten, so if you add a new entity, such as Coupon, you'll have to include that class in your projects manually - both the .cs file and .generated.cs file. Same thing with the stored procedures - they don’t get automatically included in the database project file. And if you add any custom code to your .cs files, that's also preserved.
End result: you can safely regenerate your entities without fear of losing code if you follow a few guidelines: Don't modify files in the Framework folder and add your custom code to the .cs entity file, leaving the generated.<entity>.cs entity files alone.
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
What The NuSoft Framework Gives You: Framework Components
posted on 12/19/07 at 02:00:34 am by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
I highlighted the EntityBase and plan to cover EntityList/EntityListReadOnly separately because there's so much in those ones. I'll cover the rest here. We have quite a few other components that you get in the Framework folder, and this is a quick view of what each is.
DatabaseColumnAttribute.cs: This is an attribute class that can be put on properties to signify to EntityBase's Initialize() method to expect that the property will be in the data row currently being loaded. By specifying an argument in the constructor, you can tell Initialize() to load this property with a field in the datarow by the name specified:
1: private int _numberOfOrders
2: [DatabaseColumn("NumOrders")]
3: public string NumberOfOrders
4: {
5: get { return _numberOfOrders; }
6: protected set { _numberOfOrders = value; }
7: }
This will look for a column with a name of NumOrders (reader["NumOrders"]) to load into the NumberOfOrders property.
EntityEventArgs.cs and EntityCancelEventArgs.cs: These are our custom event argument classes, and one or the other is used in all of the events in EntityBase. EntityEventArgs is used in the Init, PropertyChanged, Saved, Updated, Inserted and Deleted events. It inherits from EventArgs, and adds on an Entity property, which will be the entity that the event is happening on. For Saved, Updated, Inserted and Deleted, it will also have the SqlHelper set on it, so that you can do work with the database and have it partipate in the current transaction. EntityCancelEventArgs inherits from CancelEventArgs, but adds on an Entity property and the current SqlHelper so you can participate in the current transaction. It is used in the Saving, Updating, Inserting, and Deleting events. By setting the Cancel property to true, you can prevent the pending operation from occurring:
1: public void Customer_Saving(object sender EntityCancelEventArgs e)
2: {
3: e.Cancel = true;
4: }
This will prevent the pending save operation from happening, and will leave your entity in a dirty state.
SqlHelper.cs: This is your everyday typical SqlHelper helper class that's used on most projects that don't use Enterprise Library. We made one modification - it's not a static class, and it implements IDisposable, which means that you can use it in a using block, and have your connection cleaned up for you automatically:
1: using (SqlHelper helper = new SqlHelper())
2: {
3: helper.Execute(
"update Orders set ShippedDate = GetDate() where OrderId = 1", CommandType.Text, null);
4: }
This helps us ensure that our data connections are cleaned up for us, because in the Dispose method, we check for an open connection, and if it's open, the helper closes it.
ISearchable.cs and ISavable.cs: These are interfaces that define how you can use an entity. ISearchable is implemented by our EntityList, meaning you can search the list for a particular entity in the list. ISavable is implemented by the EntityBase and specifies that the entity can be saved.
MinToEmptyTypeConverter.cs: This is a type converter that specifies that if a property has a minimum value, it will be displayed as an empty string. For example, an int property will be defaulted to int.MinValue. This ensures that when you display this value in your UI, it will be displayed as String.Empty rather than -241213213 or whatever the minimum value for an int is.
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
Pay It Square In The News
posted on 12/18/07 at 09:00:04 pm by Joel Ross
Pay It Square, a site I helped my partner develop, was recently featured in the Grand Rapids Business Journal.
The article itself, other than the first line, is protected, but you can see a picture of Brian and I at the NuSoft office, with the company Foosball table!
Categories: Develomatic
What The NuSoft Framework Gives You: EntityBase
posted on 12/17/07 at 09:00:05 pm by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
Let's dive deeper into some of the files in the framework and see what they offer us, and how we can exploit them for our own benefit. We'll start with EntityBase.
EntityBase is the root of all of our entities. It defines what an entity should support as well as providing utility methods for loading entities and mapping database values to .NET values.
We'll start from the beginning and look at the constructors. There's three of them, and each one has it's own purpose. The first is a protected default constructor and will be called whenever a default constructor is called for an entity. It calls the OnInit method, which fires the Init event, but more importantly, OnInit can be overridden in our entities to allow us to set default values for fields. The second and third constructors take either a IDataReader or a DataRowView. Both call the OnInit method (and thus, fire the Init event), but after that, it loads data into the object from a record in the database.
Next, let's take a look at the properties EntityBase gives you. There's three: IsNew, IsDirty, and IsDeleted. These are all public properties (with internal sets) that are used to maintain the state of the object. The default for these are that the object is new, dirty, and not deleted. Every time a property is set on an entity, IsDirty will be set to true. Calling Save() (see below) will set IsDirty back to false.
The public methods on EntityBase are Save() Delete(), Clone(), Rollback(), and SetDeleted(). These are what the external applications will call. The first two methods are essentially the same - they create a SqlHelper and start a transaction, then call the protected Save/Delete method passing in the SqlHelper. Clone is what allows us to be ICloneable, and it can create either a shallow or deep copy of our objects. A shallow copy will only grab primitive property types, while a deep copy will grab copies of entities that are related to the cloned entity. Being able to clone an object is what allows us to support rollbacks of our objects. Rollback is what actually rolls back changes to an entity. SetDeleted flags the entity as deleted, but it does not actually delete the entity until you call the save method on it or one of it's parents.
There are four main methods that deal with getting data in and out of the database. They all take a SqlHelper as an argument, and they are Save(), Update(), Delete() and Insert(). This allows our objects to persist themselves. There are utility-type functions that worry about getting parameters for data access, so those get built automatically for us. This version of save is more of a distributer. It doesn't touch the database directly at all. If the entity is new, it'll call Insert. If it's not new, but dirty, it will call Update, and if it's deleted, it will call Delete. It also calls UpdateChildren, which will call Save on any EntityList-based collections it has (such us OrderDetails on an Order entity). The Insert, Update, and Delete methods basically handle their designated operation, including getting the parameters ready for the database. Note also that Insert and Update return the record it's working on (using SQL Server 2005's output keyword), so that any changes to the object by the database are automatically pulled back into the object, such as things changed by a trigger or identity column values.
Passing around the SqlHelper into the Save, Delete, Insert, and Update methods allows each operation to take part in the same transaction - including saves done on child objects - and if an operation fails any place in the save process, the whole transaction can be rolled back, ensuring your database will be left in a stable state.
The last protected method in EntityBase is Initialize, and it's overloaded to take either an IDataReader or a DataRowView. It is responsible for loading each property from the data record. It uses reflection to find any properties marked with the DatabaseColumnAttribute, and then tries to find it in the data record. If found, it loads the property, and if not, it doesn't do anything - no error either. Lastly, to ensure our entity is in a pristine state, we flag it as not new (it's been loaded from the database) and not dirty.
Before we move on to the static methods, let's touch on the events defined in EntityBase. There are 4 sets of "-ing" and "-ed" events that happen before ("-ing") and after ("-ed") a method's guts take place. We use a custom EventArgs class which takes the current SqlHelper, allowing any custom database updates written as part of the event handlers can participate in the transaction. Here's the 10 events we have:
- Init: This event fires when the object is instantiated.
- Saving: This event fires before an entity is saved. It can be canceled through the event arguments.
- Saved: This event fires after the entity has been saved.
- Deleting: This event fires before an entity is deleted. It can be canceled through the event arguments.
- Deleted: This event fires after the entity has been deleted.
- Inserting: This event fires before an entity is inserted. It can be canceled through the event arguments.
- Inserted: This event fires after the entity has been inserted.
- Updating: This event fires before an entity is updated. It can be canceled through the event arguments.
- Updated: This event fires after the entity has been updated.
- PropertyChanged: This event fires anytime a property value changes. This is mainly so we can support two-way binding.
Let's move on to the static methods/properties that the EntityBase gives you. We'll start with properties, because that's the easiest. Depending on whether you are using stored procedures or dynamic SQL, you'll have properties for either the stored procedures to do inserts and updates, or the SQL to do an insert or update. If you use Dynamic SQL, you'll also get a property called SelectFieldList which returns the fields in the table, so you can use it in your custom queries and not have to rewrite all of your custom queries if you add a field later. Dynamic SQL also gives you one other: DefaultSortOrder. This is the sort order that static GetList methods will use to determine the sort order a query should use when executed. But it's in the base, and has a value of "" - how does that help you? Well, you can't exactly override static properties, but you can hide them, so in your entity, you can do this:
1: public static new string DefaultSortOrder
2: {
3: get { return "order by OrderID";
4: }
Adding the new keyword tells any queries in that class to use the new value for DefaultSortOrder when it runs it's queries. If you're using Stored Procedures, we don't have anything like this, so you'll have to modify your get list stored procedures by hand if you want a custom sort order (or you could use the EntityComparer to sort them in memory).
The last component of the EntityBase is it's static methods. None are public, and all are meant to be used by the entities that inherit from EntityBase. First up are a dozen or so methods that take primitive types and determine if they still have a default value, and if so, return a DBNull value. This allows us to pull a null out of a database, and if it's never changed, ensure that the null gets back into the database (and not the default value used by .NET). The methods are all called GetDatabaseValue, and are differentiated by the input parameter type.
The last set of methods are used to instantiate objects and get lists of objects. GetEntityInstance<T>() will find a default constructor (either public or non-public), and return an instance, using reflection. This'll be called later by the static Initialize methods.
While the other static methods are valuable, the three most useful ones, at least to entities inheriting from EntityBase, are GetOne<T>(), GetList<T>(), and GetListReadOnly<T>. These methods take a command and parameters, and run the command against the database, getting back a data reader. They then call either Initialize<T>() (called by GetOne) or InitializeList<T>() (called by GetList and GetListReadOnly) to get either one entity or a list of entities. We do specify that T has to have EntityBase in it's object hierarchy.
Initialize and InitializeList both have overloads that take in data readers or data views (lists) / data row views (non lists), but they both do roughly the same thing: construct an entity, and then call Initialize on that entity, passing in the data row. The only real difference is that InitializeList creates a collection of entities, and does the entity creation in a loop.
And that's it! There's a lot that is done for you and provides a lot of flexibility in how it can be used by entities that inherit from it.
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
What The NuSoft Framework Gives You
posted on 12/16/07 at 09:00:11 pm by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
Ok. You just got done sitting back and relaxing, and now you've got your generated framework. Great. Now you're ready to go!
But what exactly did you get?
In your root folder, you should have a solution file, named after your Namespace you used while generating. You'll also have another folder named after your DomainNamespace you used when generating. If you selected StoredProcedures for your SQLType, then you'll also have a Database folder.
In that Database folder, you'll find a database project, with the standard folders it starts with, but under the Create Scripts folder, you have a Stored Procedures folder, which has all of your scripts in it. They are all in separate files, and you'll need to run these against the database before you get too far.
In the other folder (Domain, Business, Core, or whatever you called it) is the core of the framework. The files in here are your business entities. Each one has a <entity>.cs file and a Generated.<entity>.cs file. When you open the project, the Generated.<entity>.cs file will be a sub-item of the <entity>.cs file, so it's there, but hidden.
In the Framework folder, you'll find the standard files for the NuSoft Framework:
- EntityBase - every entity inherits from EntityBase. This provides a default set of methods and properties, default implementations for some methods, and a definition of a standard interface our entities will have. This is also responsible for loading and initializing entities from database readers.
- EntityBaseReadOnly - This is used for any classes that have no primary key. You can get a list of all of them and you can get them by primary key. You can't modify it, and you can't create new ones.
- EntityList - this provides us with a custom generic collection we can use for lists of our entities. It handles loading and saving of child collections for an entity.
- EntityListReadOnly - this gives us a way to have a read-only collection that will not get saved when we update our entities.
- DatabaseColumnAttribute - we use this attribute in our entities to flag a column as coming from the database. You can use this in your own custom objects to have them loaded automatically by the framework.
- EntityEventArgs and EntityCancelEventArgs - our entities have a few events that you can handle, and we pass along custom event arguments. We typically have an "ing" and a "ed" event - Saving and Saved, Inserting and Inserted, etc. The "ing" event will use the EntityCancelEventArgs, which allows the handler to cancel the pending event. So by saying e.Cancel = true; in the Saving event, we can prevent a Save from happening.
- SqlHelper - this is a fairly standard SQL Helper class that allows us to access SQL Server. It relies on a ConnectionString being in the ConnectionStrings section of your config file, based on the property you set for the connection string key.
- ISearchable and ISavable: Two interfaces that are used to specifiy that our list is searchable and that our entities are savable.
I'll go into more details on the framework files at another time. For now, that's good enough to get you going.
Back to the domain folder. The <entity>.cs file will be where you'll add your custom code. The Generated.<entity>.cs file is designed to be left alone, so it can be regenerated if you need to. This isn't a hard and fast rule, but be aware that the file will get overwritten if you regenerate.
The Generated.<entity>.cs file has most of the guts of your business entities in it. It has all of the properties for each database column. It has Properties for foreign key relationships - an Order will have a Customer property for the customer who owns the order. It will also have collections for entites that have foreign keys to it - an Order will have a collection of OrderDetails. There are static methods to create each object, as well as member methods to create objects - if you have an Order object called order, you can call order.CreateOrderDetail(); to get a new OrderDetail object that is related to the Order. It has a public Save() method that knows what children objects need to be saved - if you have a customer entity, change the name, add an order with two order lines, and call customer.Save() it will update the customer, insert the Order and insert all of the OrderDetail rows. There's more, which I'll cover later in more details.
Lastly, you'll get a project file that includes all of the above files.
Open the solution, build, and you're ready to add your UI to the solution!
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
Getting Started With The NuSoft Framework
posted on 12/15/07 at 09:00:47 pm by Joel Ross
While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.
How and when do you use the NuSoft Framework? Well, there's two major requirements before you can get going with it. First, you need CodeSmith 3.2 or later. It does not require the full version of CodeSmith Professional - you can generate with the $99 basic version.
It also requires that your database be completed. Most custom development projects start from a database model and work their way up, and that's exactly how this works. Once your database is in place, you can start generating code.
When you unzip the templates, you'll find a Main.cst file in the root folder. That's the file you want to use for your initial generation. When you open it, you'll see a few properties that you need to set:
- SourceDatabase - this is the database you want to generate against. You'll probably have to create a new connection to the database, which basically involves building a connection string and giving it a name. We use certain features that require SQL Server 2005, however, it will work with SQL Server Express.
- ExcludedTables - here you can exclude tables from having classes generated for them. If you have ASP.NET membership tables, you'll want to exclude those.
- TablePrefixes: this is a comma delimited list of prefixes to strip off tables for class names. For example, if all of your tables start with tbl, then the entity generated from tblOrders will be Order.
- Namespace: This is the root namespace for the whole application. I typically use <CompanyName>.<ProductName>.
- DomainNamespace: This is the namespace to add to the root namespace for your Domain / Business objects. We default to Business, but we've also used Domain and Core in the past. The resulting namespace for your business objects will be <Namespace>.<DomainNamespace>.
- SQLType: We support both Stored Procedures and Dynamic SQL. You can choose either one. If you choose Stored Procedures, we'll generate a database project with all of the stored procedures used by the default framework implementation.
- OutputDirectory: The most important one! This is where you'll get your code when it's done.
Once you have that done, just click Generate, sit back, and relax. In a few seconds, your framework will be ready to go.
Tags: NuSoft Framework | Code Generation | CodeSmith
Categories: Development, Software, RCM Technologies
I'm Going to Disney World!
posted on 12/15/07 at 12:42:40 am by Joel Ross
Tomorrow at 4:30, I'm getting out of the cold and we're heading for Sunny Orlando. Well, next week it looks more like Partly Sunny Orlando, but considering it's 25 degrees here in West Michigan, I'll take partly sunny and upper 60's over snow and upper teens! We're planning to go to Disney (first time for me and my daughters. The Wife has been there, but not for a long time), and Sea World, so it should be a good time.
Anyway, I'll be gone for the next week, returning next Saturday afternoon. I'm not taking a laptop, and I have no idea about connectivity down there, so I'm not planning to tweet or respond to email either. But that doesn't mean this blog will be silent! I have a post scheduled to "go live" every day next week, most about some basic info on the NuSoft Framework. I obviously won't be responding to comments until I get back, but I will do so at that time.
Oh, and if everyone could stop emailing, tweeting, and blogging for me, that'd help so I'm not too far behind when I get back!
Categories: Personal
Continuous Integration For One Person Projects?
posted on 12/14/07 at 08:21:43 pm by Joel Ross
Scott Reynolds put together a nice post giving high level reasoning for using Continuous Integration. If you're not using CI now, but are looking for a reason to do so, he's got a nice anecdotal example.
I think it's great that Scott's put up some content for beginners. I'd love to see a lot of people put up beginner guides. Having said that, he started out with a statement I disagree with.
If you are alone, writing code with no team, no release cycle, no QA, or anything of the sort of stuff that represents professional software development, then CI is probably nothing more than mental masturbation , and a nice exercise to do that nets you no gain.
I have a few side projects that I've written, and I have my own personal CI server set up for those projects. I'm the only developer on it, but there's a reason I have CI. I know if it builds locally, it'll build on the server, so I don't get that benefit out of it. But I do get something out of it. Whenever I set up a CI server, for every project checked in, I typically create at least two CI "projects" for it. The first is a dev build. In a single person environment, this doesn't get you much. It's "mental masturbation". The other one is a Production build, which does everything in release mode and completely automates how I deploy my software. Any manual deployment steps I would take without CI are automated here.
As an example, I have a web site that I have to upload to another server. It has different web.config settings between dev and production, as well. My build process handles all of that for me - well, not completely. I don't go as far as automatically deploying, but it does drop a zip file on the web server for me so I can get it easily.
Bottom line is that even if it's just me, any part of a manual process that can be automated should be. It's not hard to zip up a release build and copy it to the web server, but it's much easier to click a button and come back later when it's done. And the more manual steps, the more likely you are to mess it up!
Tags: Continuous Integration
Categories: Development
iKitchen.com: A Place To Never Shop
posted on 12/13/07 at 09:00:52 pm by Joel Ross
About a month ago, The Wife decided we needed a new stroller for the girls while we're in Florida in a couple of weeks. It's something she's been looking at for a while, and decided it was time to pull the trigger. She found the stroller she liked, and searched for the best price. She finally found the lowest price at iKitchen.com (which is actually ekitchengadgets.com), so she placed the order.
And it's been nothing but headaches since then.
November 4th
The order was placed. The credit card was charged.
November 10th - 26th
After a week of not hearing anything, The Wife called to find out what was going on - and continued to call every few days to find out the status of her order. She never received any communication from them - she had to contact them to get any information. Every time she called, she was told "It should be shipping soon and you should have it in two to three days." Oddly, we stopped believing them after about the 4th time.
November 28th
The Wife called again, and was told they were on back order and it could be a LONG time before we get one - like March. Hmm. That's a bit longer than two to three days.
November 30th
The Wife had enough, and given that it'd been almost a full month and still had no definitive idea when or if we would ever get the stroller. She started looking around, and after finding another place to order it, she canceled her original order.
December 1st
In less than 24 hours, we got the stroller from the new place. We put it together, and it's pretty nice.
December 7th
We still hadn't seen the refund hit the credit card, so we called them back. This is best part. "Oh. When you canceled your order, you didn't ask for a refund." What? Since when are canceling an order and getting a refund two separate things? NO ONE ELSE in the world does that. That's absolutely ridiculous! After careful consideration of whether or not we wanted to leave a $250 credit with a company we're not happy with, we opted to cancel the order. They've now held our money for over a month without ever even intending to ship us a product in that timeframe.
December 13th
We finally got our money back. That took long enough. It's amazing. They can charge me instantaneously, but it takes six days to give me money back - after they've had it for over a month for no reason!
Even after finally telling us that these strollers are very hard to come by, and won't be available until early Spring, their web site still says "Usually ships in 2-3 business days." After going through what we went through, I feel sorry for anyone who sees that and believes them!
Categories: Personal