What's the difference between implementing data sources and resources in Terraform Providers?

Yes the read function for the resource can generally be easily converted into the read function for the data. However, those two items you listed above are only specific to this tutorial example of ordering coffee. You would need to adapt them accordingly for your specific provider for whatever specific subtleties exist there. To assist with this point, here is why those two modifications are necessary.

Top level ID attribute -

In the resource, this would be determined during a Create or Update function operation. Therefore, this would be computed during the code execution and not known ahead of time. As stated in the tutorial, the code execution generates an order ID from the API and then the provider stores it for the ID in the state. In the data source, this ID already exists because only a Read function executes a Read operation on a pre-existing resource, so that ID does exist ahead of time. If the ID was a top level attribute for the resource, then it would also need to be able to "see the future" to determine the order and resource ID before it exists.

Items is required not computed -

Not all of your attributes will be computed in the resource every time. That just happens to be the situation in this tutorial. The reason this becomes required is similar to the reasoning above. During the Create or Update function, these attributes may not be specified in the resource argument in the Terraform config, and therefore the attributes are computed during code execution. The functions invoke against the API endpoint, and the API returns values for these attributes in the response payload. Therefore, the attribute values are "computed", and not specified as input values to the arguments corresponding to the attributes. For the data source, these would become required because they all exist ahead of time, and they are not being computed. Additionally, they would need to be specified so the Read function correctly identifies the desired resource and returns only one resource for the data source.

strconv.Itoa(d.Get("id").(int))

This occurs because the returned ID in the response payload from the API is an integer:

order = {
  "id" = 1
  "items" = [
    {
      "coffee_description" = ""
      "coffee_id" = 1
      "coffee_image" = "/packer.png"
      "coffee_name" = "Packer Spiced Latte"
      "coffee_price" = 350
      "coffee_teaser" = "Packed with goodness to spice up your images"
      "quantity" = 4
    },
  ...
}

It needs to be converted to a string after the public Get function (which expects a type int as seen in the code) because that is the expected type for the ID for Terraform. If the ID is already a string type, then it does not need to be converted.